<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://pscustomobject.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://pscustomobject.github.io/" rel="alternate" type="text/html" /><updated>2026-03-02T12:57:54+00:00</updated><id>https://pscustomobject.github.io/feed.xml</id><title type="html">PsCustom Object - Hitchikers GUID(e) to Automation</title><subtitle>Hitchikers GUID(e) to Automation</subtitle><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><entry><title type="html">Getting Started with .gitignore</title><link href="https://pscustomobject.github.io/git/version-control/getting-started-with-gitignore/" rel="alternate" type="text/html" title="Getting Started with .gitignore" /><published>2025-05-04T00:00:00+00:00</published><updated>2025-05-04T00:00:00+00:00</updated><id>https://pscustomobject.github.io/git/version-control/getting-started-with-gitignore</id><content type="html" xml:base="https://pscustomobject.github.io/git/version-control/getting-started-with-gitignore/"><![CDATA[<blockquote>
  <p>🧰 This post kicks off a new series on Git essentials—starting with <code class="language-plaintext highlighter-rouge">.gitignore</code>, a simple yet powerful tool for managing what gets tracked in your repositories.</p>
</blockquote>

<hr />

<h2 id="️-what-is-gitignore">🗃️ What is <code class="language-plaintext highlighter-rouge">.gitignore</code>?</h2>

<p>When working with Git, it’s common to have files that shouldn’t be tracked—such as build artifacts, temporary files, or sensitive information. The <code class="language-plaintext highlighter-rouge">.gitignore</code> file allows you to specify patterns for files and directories that Git should ignore.</p>

<p>By placing a <code class="language-plaintext highlighter-rouge">.gitignore</code> file in your repository’s root directory, you instruct Git to disregard specified files, keeping your version history clean and focused.</p>

<hr />

<h2 id="-creating-a-gitignore-file">📝 Creating a <code class="language-plaintext highlighter-rouge">.gitignore</code> File</h2>

<p>To create a <code class="language-plaintext highlighter-rouge">.gitignore</code> file:</p>

<ol>
  <li>Navigate to your repository’s root directory.</li>
  <li>
    <p>Create the <code class="language-plaintext highlighter-rouge">.gitignore</code> file:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">touch</span> .gitignore
</code></pre></div>    </div>
  </li>
  <li>Open the file in your preferred text editor and add patterns for files/directories to ignore.</li>
</ol>

<p>Example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Ignore node_modules directory</span>
node_modules/

<span class="c"># Ignore all .log files</span>
<span class="k">*</span>.log

<span class="c"># Ignore build output</span>
dist/
</code></pre></div></div>

<hr />

<h2 id="-applying-gitignore-to-already-tracked-files">🔄 Applying <code class="language-plaintext highlighter-rouge">.gitignore</code> to Already Tracked Files</h2>

<p>If you’ve already committed files that should be ignored, updating <code class="language-plaintext highlighter-rouge">.gitignore</code> won’t remove them from the repository. To stop tracking these files:</p>

<ol>
  <li>
    <p>Remove the files from the index:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git <span class="nb">rm</span> <span class="nt">--cached</span> filename
</code></pre></div>    </div>
  </li>
  <li>
    <p>Commit the changes:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit <span class="nt">-m</span> <span class="s2">"Remove ignored files from tracking"</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Push the changes to your remote repository.</p>
  </li>
</ol>

<hr />

<h2 id="-global-gitignore">🌐 Global <code class="language-plaintext highlighter-rouge">.gitignore</code></h2>

<p>For patterns that should apply to all your Git repositories (e.g., OS-specific files like <code class="language-plaintext highlighter-rouge">.DS_Store</code>), you can set up a global <code class="language-plaintext highlighter-rouge">.gitignore</code>:</p>

<ol>
  <li>
    <p>Create a global <code class="language-plaintext highlighter-rouge">.gitignore</code> file:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">touch</span> ~/.gitignore_global
</code></pre></div>    </div>
  </li>
  <li>
    <p>Configure Git to use this file:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> core.excludesFile ~/.gitignore_global
</code></pre></div>    </div>
  </li>
</ol>

<p>Then add your global ignore patterns to <code class="language-plaintext highlighter-rouge">~/.gitignore_global</code>.</p>

<hr />

<h2 id="-tips-and-best-practices">🧪 Tips and Best Practices</h2>

<ul>
  <li><strong>Use comments</strong>: Prefix lines with <code class="language-plaintext highlighter-rouge">#</code> to explain ignore rules.</li>
  <li><strong>Be specific</strong>: Avoid overly broad patterns that may unintentionally ignore important files.</li>
  <li><strong>Leverage templates</strong>: Use <a href="https://github.com/github/gitignore">GitHub’s official <code class="language-plaintext highlighter-rouge">.gitignore</code> templates</a> for popular languages, editors, and frameworks.</li>
</ul>

<hr />

<p>By effectively using <code class="language-plaintext highlighter-rouge">.gitignore</code>, you maintain a clean and efficient repository, free from unnecessary files and potential security risks.</p>

<p>Stay tuned for the next post in this series, where we’ll explore branching strategies and how to manage them effectively.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="git" /><category term="version-control" /><category term="git" /><category term="gitignore" /><category term="version-control" /><category term="best-practices" /><summary type="html"><![CDATA[🧰 This post kicks off a new series on Git essentials—starting with .gitignore, a simple yet powerful tool for managing what gets tracked in your repositories.]]></summary></entry><entry><title type="html">🚀 Launching My Cloud &amp;amp; DevOps Notes Hub</title><link href="https://pscustomobject.github.io/devops/notes/launching-my-cloud-devops-notes-hub/" rel="alternate" type="text/html" title="🚀 Launching My Cloud &amp;amp; DevOps Notes Hub" /><published>2025-05-02T00:00:00+00:00</published><updated>2025-05-02T00:00:00+00:00</updated><id>https://pscustomobject.github.io/devops/notes/launching-my-cloud-devops-notes-hub</id><content type="html" xml:base="https://pscustomobject.github.io/devops/notes/launching-my-cloud-devops-notes-hub/"><![CDATA[<p>After months of collecting learning materials, writing study plans, and documenting hands-on experiments in Markdown, I decided it was time to structure and publish my technical notes as a proper GitHub Pages site.</p>

<p>🎉 <strong>It’s live here</strong>: <a href="https://pscustomobject.github.io/terraforming-my-career/">Terraforming My Career</a></p>

<hr />

<h2 id="-what-youll-find">📘 What You’ll Find</h2>

<p>The new site currently includes:</p>

<ul>
  <li>🛠 <strong>Terraform</strong>: from getting started to change management and teardown</li>
  <li>📚 <strong>Learning Plans</strong>: a structured timeline to guide my Cloud &amp; DevOps path</li>
  <li>🐳 Sections for <strong>Docker</strong>, <strong>Kubernetes</strong>, and <strong>AWS</strong> (in progress)</li>
  <li>📖 <strong>Reading Notes</strong> from books like <em>Terraform: Up &amp; Running</em></li>
</ul>

<p>Everything is structured in folders, rendered with <a href="https://just-the-docs.github.io/">Just the Docs</a>, and completely searchable.</p>

<hr />

<h2 id="-why-im-doing-this">🔍 Why I’m Doing This</h2>

<p>I believe in learning in public. Publishing my notes helps me:</p>

<ul>
  <li>Stay accountable to my learning goals</li>
  <li>Give back to others walking a similar path</li>
  <li>Build writing discipline and clarity of thought</li>
</ul>

<p>Over time, this space will grow into a personal knowledge base and lab journal — and you’re welcome to follow along.</p>

<hr />

<p>💡 <em>Want to build something similar?</em>
Check out the <a href="https://github.com/PsCustomObject/terraforming-my-career">GitHub repo</a>, fork it, and feel free to reach out.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="DevOps" /><category term="Notes" /><category term="Cloud" /><category term="DevOps" /><category term="Terraform" /><category term="GitHub Pages" /><category term="JustTheDocs" /><summary type="html"><![CDATA[I’ve launched a dedicated GitHub Pages site to publish my notes, learning plans, and technical documentation as I transition into a Cloud & DevOps role.]]></summary></entry><entry><title type="html">SSH Connection Manager in Command Line</title><link href="https://pscustomobject.github.io/linux/howto/SSH-Connection-Manager/" rel="alternate" type="text/html" title="SSH Connection Manager in Command Line" /><published>2024-09-20T00:00:00+00:00</published><updated>2024-09-20T00:00:00+00:00</updated><id>https://pscustomobject.github.io/linux/howto/SSH-Connection-Manager</id><content type="html" xml:base="https://pscustomobject.github.io/linux/howto/SSH-Connection-Manager/"><![CDATA[<h2 id="managing-ssh-connections">Managing SSH Connections</h2>

<p>In the Windows ecosystem, tools like Remote Desktop Manager help handle and manage multiple connections to the various servers within the infrastructure. But how can we achieve something similar for SSH connections to frequently accessed servers in Linux?</p>

<p>Let’s explore how this can be done efficiently in the command line.</p>

<h2 id="ssh-config-file">SSH Config file</h2>

<p>First, if it doesn’t already exist, create the .ssh directory in a convenient location (I keep mine in my $HOME directory):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> .ssh
</code></pre></div></div>

<p>Next, create a file named config, adjust its default permissions, and open it with your favorite text editor:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">touch </span>config

<span class="nb">chmod </span>600 config

vim config
</code></pre></div></div>

<p>This file is automatically sourced by the terminal, allowing you to easily connect to machines defined in it. Here’s a sample of the configuration I use to manage my servers:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host node_01
        Hostname node01.lab.com
        User pscustomobject
        IdentityFile /home/pscustomobject/.ssh/id_rsa
Host node_02
        Hostname node02.example.com
        User adminuser
        Port 4242

<span class="c">## Set connection defaults for all hosts, this is overriden by host options</span>
Host <span class="k">*</span>
     ForwardAgent no
     ForwardX11 no
     ForwardX11Trusted <span class="nb">yes
     </span>User pscustomobject
     Port 22
     Protocol 2
     ServerAliveInterval 60
     ServerAliveCountMax 30

</code></pre></div></div>

<h2 id="key-notes">Key Notes</h2>

<ul>
  <li>The IdentityFile directive points to the location of your private key. This can be stored in a shared or networked location for easier access across multiple devices.</li>
  <li>The *Host ** section defines defaults for all hosts, which individual host entries can override.</li>
</ul>

<p>Once the SSH configuration is in place, you can connect to any of the defined hosts simply by issuing the command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh node_01
</code></pre></div></div>

<p>Better yet, tab completion will work just as it does for standard commands, so you don’t have to remember the exact name of each node.</p>

<h2 id="additional-tips">Additional Tips</h2>

<p>I highly recommend reading the <em>ssh_config</em> man page (<em>man ssh_config</em>) to discover the numerous other options you can use in your config file to further streamline and simplify SSH connection management.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="Linux" /><category term="Howto" /><category term="Linux" /><category term="Tips" /><category term="HowTo" /><summary type="html"><![CDATA[Managing multiple SSH connections and hosts can sometimes be problematic. In this post, I will show how you can easily save multiple connections directly in the terminal (Linux) to take advantage of tab completion, passwordless authentication, and more.]]></summary></entry><entry><title type="html">PowerShell - Generate Unique UPN</title><link href="https://pscustomobject.github.io/powershell/howto/PowerShell-Generate-Unique-Upn/" rel="alternate" type="text/html" title="PowerShell - Generate Unique UPN" /><published>2024-07-27T00:00:00+00:00</published><updated>2024-07-27T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/howto/PowerShell-Generate-Unique-Upn</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/howto/PowerShell-Generate-Unique-Upn/"><![CDATA[<h2 id="generating-unique-user-principal-names-upns-in-powershell">Generating Unique User Principal Names (UPNs) in PowerShell</h2>

<p>When managing Active Directory (AD) environments, creating unique User Principal Names (UPNs) is a common, and challenging!, task.</p>

<p>This blog post covers two PowerShell functions that help ensure UPN uniqueness: <strong>Test-UPNExist</strong> and <strong>Get-UniqueUPN</strong>. These functions can be particularly useful for automating user creation processes and can be integrated into your automation projects and solutions.</p>

<p><strong>Note</strong> Both cmdlets are still under active development so do expect some changes, as I progres with development I will update the post. Feel free to report any issue, idea or suggestion so that I can integrate it into the final cmdlet version.</p>

<h3 id="function-1-test-upnexist">Function 1: Test-UPNExist</h3>

<p>This is a support that is used to check if given UPN already exists in the AD environment.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Test-UPNExist</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
        Cmdlet will check if a given UPN exists in the forest.
    
    </span><span class="cs">.DESCRIPTION</span><span class="cm">
        Cmdlet is a diagnostic tool to check if a given UPN is already assigned to a user in the forest.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> UPN
        A string representing the UPN to check for uniqueness.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> AdServer
        A string representing the name of the domain controller to be used for the check, if parameter
        is not specified the closest Global Catalog is used.
    
    </span><span class="cs">.EXAMPLE</span><span class="cm">
        PS C:\&gt; Test-UPNExist -UPN 'John.Doe@example.com'
#&gt;</span><span class="w">
    
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$UPN</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$AdServer</span><span class="w">
    </span><span class="p">)</span><span class="w">
    
    </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$AdServer</span><span class="p">)</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$true</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$adForest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.DirectoryServices.ActiveDirectory.Forest</span><span class="p">]::</span><span class="n">GetCurrentForest</span><span class="p">()</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ldapPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'{0}{1}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="s1">'GC://'</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="p">(</span><span class="nv">$adForest</span><span class="o">.</span><span class="nf">FindGlobalCatalog</span><span class="p">()</span><span class="o">.</span><span class="nf">Name</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ldapPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'{0}{1}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="s1">'LDAP://'</span><span class="p">,</span><span class="w"> </span><span class="nv">$AdServer</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="c"># Instantiate required objects and run query</span><span class="w">
    </span><span class="nv">$adDomain</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.DirectoryServices.DirectoryEntry</span><span class="p">(</span><span class="nv">$ldapPath</span><span class="p">)</span><span class="w">
    </span><span class="nv">$adSearcher</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.DirectoryServices.DirectorySearcher</span><span class="p">(</span><span class="nv">$adDomain</span><span class="p">)</span><span class="w">
    </span><span class="nv">$adSearcher</span><span class="o">.</span><span class="nf">SearchScope</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Subtree'</span><span class="w">
    </span><span class="nv">$adSearcher</span><span class="o">.</span><span class="nf">PageSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1000</span><span class="w">
    </span><span class="nv">$adSearcher</span><span class="o">.</span><span class="nf">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"(&amp;(objectCategory=person)(userPrincipalName=</span><span class="nv">$UPN</span><span class="s2">))"</span><span class="w">
    </span><span class="p">[</span><span class="n">void</span><span class="p">](</span><span class="nv">$adSearcher</span><span class="o">.</span><span class="nf">PropertiesToLoad</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"userPrincipalName"</span><span class="p">))</span><span class="w">
    
    </span><span class="p">[</span><span class="n">array</span><span class="p">]</span><span class="nv">$searchResult</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$adSearcher</span><span class="o">.</span><span class="nf">FindOne</span><span class="p">()</span><span class="w">
    
    </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$searchResult</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Here’s a summary explanation of the parameters:</p>

<ul>
  <li><strong>Parameters</strong>
    <ul>
      <li><em>$UPN</em> - A string representing the UPN to check</li>
      <li><em>$Server</em> -  A string representing the name of the  LDAP server to query. Parameter is optional and if omitted function will automatically select the closest global catalog server</li>
    </ul>
  </li>
</ul>

<p>Here’s an example usage:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$upnExists</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Test-UPNExist</span><span class="w"> </span><span class="nt">-UPN</span><span class="w"> </span><span class="s2">"john.doe@example.com"</span><span class="w">

</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$upnExists</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="c"># Redundant I know but I like to make code redeable :-)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"The UPN exists."</span><span class="w">
</span><span class="p">}</span><span class="w"> 
</span><span class="kr">else</span><span class="w"> 
</span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"The UPN does not exist."</span><span class="w">
</span><span class="p">}</span><span class="w">

</span></code></pre></div></div>

<h3 id="function-1-test-upnexist-1">Function 1: Test-UPNExist</h3>

<p>This function generates a unique UPN based on given name components and ensures it doesn’t already exist in the AD forest.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-UniqueUPN</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
        Cmdlet will generate a forest wide unique UPN.
    
    </span><span class="cs">.DESCRIPTION</span><span class="cm">
        Cmdlet will generate a forest wide unique UPN according to generation rules
        defined by the user.
        
        Cmdlet accept different types of objects to generate the UPN to allow greater flexibility
        
        ADObject - For example and object from Get-AdUser cmdlet
        Strings - Representing First Name, Last Name etc.
        DirectoryService Objects - For example when using native .Net methods to retrieve the identity
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> ADObject
        An ADObject for example output of the Get-ADUser cmdlet
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> FirstName
        A string representing the First Name of the user
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> LastName
        A string representing the Last Name of the user
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> MiddleName
        A string representing the Middle Name of the user, parameter is optional.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> UPNSuffix
        A string representing the UPN suffix to be used.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> FirstNameFormat
        A string representing the format to be for the First Name part of the UPN.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> LastNameFormat
        A string representing the format to be for the Last Name part of the UPN.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> IncludeMiddleName
        When paramenter is specified user Middle Name, if present, will be included in the UPN generation process.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> ADServer
        A string representing the name of the AD Domain Controller that will be used to query Active Directory.
    
        If no server is specified the closest Global Catalog will be automatically selected.
    
    </span><span class="cs">.PARAMETER</span><span class="cm"> Separator
        A string representing the separator to be used between UPN parts, defaults to a '.'.
#&gt;</span><span class="w">
    
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">(</span><span class="bp">DefaultParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Strings'</span><span class="p">)]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ADObject'</span><span class="p">,</span><span class="w">
                   </span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">object</span><span class="p">]</span><span class="nv">$ADObject</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Strings'</span><span class="p">,</span><span class="w">
                   </span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$FirstName</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Strings'</span><span class="p">,</span><span class="w">
                   </span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$LastName</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Strings'</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$MiddleName</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$UPNSuffix</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateSet</span><span class="p">(</span><span class="s1">'FullName'</span><span class="p">,</span><span class="w"> </span><span class="s1">'FirstLetter'</span><span class="p">,</span><span class="w"> </span><span class="n">IgnoreCase</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$FirstNameFormat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Full'</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateSet</span><span class="p">(</span><span class="s1">'FullName'</span><span class="p">,</span><span class="w"> </span><span class="s1">'FirstLetter'</span><span class="p">,</span><span class="w"> </span><span class="n">IgnoreCase</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$LastNameFormat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'FullName'</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$IncludeMiddleName</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ADServer</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$Separator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="w">
    </span><span class="p">)</span><span class="w">
    
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$PSCmdlet</span><span class="o">.</span><span class="nf">ParameterSetName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'ADObject'</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">switch</span><span class="w"> </span><span class="p">(</span><span class="nv">$ADObject</span><span class="o">.</span><span class="nf">GetType</span><span class="p">()</span><span class="o">.</span><span class="nf">FullName</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="s1">'Microsoft.ActiveDirectory.Management.ADUser'</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="nf">GivenName</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="nf">Surname</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$middleName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="nf">MiddleName</span><span class="w">
                
                </span><span class="kr">break</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="s1">'System.DirectoryServices.DirectoryEntry'</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'givenName'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'sn'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$middleName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'middleName'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                
                </span><span class="kr">break</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="s1">'System.DirectoryServices.SearchResult'</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'givenName'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'sn'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$middleName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ADObject</span><span class="o">.</span><span class="n">Properties</span><span class="p">[</span><span class="s1">'middleName'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="w">
                
                </span><span class="kr">break</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="n">default</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Unsupported AD object type: </span><span class="si">$(</span><span class="nv">$ADObject</span><span class="o">.</span><span class="nf">GetType</span><span class="p">()</span><span class="o">.</span><span class="nf">FullName</span><span class="si">)</span><span class="s2">"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$FirstName</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$LastName</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$middleName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$MiddleName</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="c"># Format first name</span><span class="w">
    </span><span class="nv">$firstName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">switch</span><span class="w"> </span><span class="p">(</span><span class="nv">$FirstNameFormat</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="s1">'FullName'</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$firstName</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="s1">'FirstLetter'</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$firstName</span><span class="o">.</span><span class="nf">Substring</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="c"># Format last name</span><span class="w">
    </span><span class="nv">$LastName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">switch</span><span class="w"> </span><span class="p">(</span><span class="nv">$FirstNameFormat</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="s1">'FullName'</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$LastName</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="s1">'FirstLetter'</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$LastName</span><span class="o">.</span><span class="nf">Substring</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="c"># Use middle name</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$middleNamePart</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$IncludeMiddleName</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$MiddleName</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="s1">'{0}{1}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="nv">$Separator</span><span class="p">,</span><span class="w"> </span><span class="nv">$MiddleName</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="c"># Setup required attributes</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$baseUPN</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="s1">'{0}{1}{2}{3}@{4}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="nv">$FirstName</span><span class="p">,</span><span class="w"> </span><span class="nv">$middleNamePart</span><span class="p">,</span><span class="w"> </span><span class="nv">$Separator</span><span class="p">,</span><span class="w"> </span><span class="nv">$LastName</span><span class="p">,</span><span class="w"> </span><span class="nv">$UPNSuffix</span><span class="p">)</span><span class="o">.</span><span class="nf">ToLower</span><span class="p">()</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$uniqueUPN</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$baseUPN</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$counter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
    
    </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="n">Test-UPNExist</span><span class="w"> </span><span class="nt">-UPN</span><span class="w"> </span><span class="nv">$uniqueUPN</span><span class="w"> </span><span class="nt">-Server</span><span class="w"> </span><span class="nv">$ADServer</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$uniqueUPN</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'{0}{1}@{2}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="p">(</span><span class="nv">$baseUPN</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="s1">'@'</span><span class="p">)[</span><span class="mi">0</span><span class="p">]),</span><span class="w"> </span><span class="nv">$counter</span><span class="p">,</span><span class="w"> </span><span class="nv">$UPNSuffix</span><span class="w">
        
        </span><span class="nv">$counter</span><span class="o">++</span><span class="w">
    </span><span class="p">}</span><span class="w">
    
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$uniqueUPN</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Here’s a summary explanation of the parameters:</p>

<ul>
  <li><strong>Parameters</strong>
    <ul>
      <li><em>$ADObject</em> - We can pass an existing AD object to the function, for example output from the <em>Get-AdUser</em> cmdlet</li>
      <li><em>$FirstName</em> - A string representing the <em>First Name</em> of a given user</li>
      <li><em>$LastName</em> - A string representing the <em>Last Name</em> of a given user</li>
      <li><em>MiddleName</em> - A string representing the <em>Middle Name</em> of a given user (of course optional)</li>
      <li><em>$UPNSuffix</em> - A string representing the domain suffix for the UPN</li>
      <li><em>FirstNameFormat</em> - The format of the first name in the UPN (e.g. Full or First letter of name)</li>
      <li><em>IncludeMiddleName</em> - Switch parameter indicating to cmdlet if Middle Name should be used or not in the UPN generation</li>
      <li><em>$Server</em> - A string representing the name of the  LDAP server to query. Parameter is optional and if omitted function will automatically select the closest global catalog server</li>
      <li><em>$Separator</em> - The separator to use between the name parts. If not specified a ‘.’ (dot) is used</li>
    </ul>
  </li>
</ul>

<p>Below you can find couple examples of functions at work</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Using string parameters</span><span class="w">
</span><span class="nv">$uniqueUPN</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-UniqueUPN</span><span class="w"> </span><span class="nt">-FirstName</span><span class="w"> </span><span class="s2">"John"</span><span class="w"> </span><span class="nt">-LastName</span><span class="w"> </span><span class="s2">"Doe"</span><span class="w"> </span><span class="nt">-UPNSuffix</span><span class="w"> </span><span class="s2">"example.com"</span><span class="w"> </span><span class="nt">-FirstNameFormat</span><span class="w"> </span><span class="s2">"FirstLetter"</span><span class="w"> </span><span class="nt">-IncludeMiddleName</span><span class="w"> </span><span class="nt">-MiddleName</span><span class="w"> </span><span class="s2">"A"</span><span class="w">

</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Generated UPN: </span><span class="nv">$uniqueUPN</span><span class="s2">"</span><span class="w">

</span><span class="c"># Using an AD object</span><span class="w">
</span><span class="nv">$adUser</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ADUser</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="s2">"johndoe"</span><span class="w">
</span><span class="nv">$uniqueUPN</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-UniqueUPN</span><span class="w"> </span><span class="nt">-ADObject</span><span class="w"> </span><span class="nv">$adUser</span><span class="w"> </span><span class="nt">-UPNSuffix</span><span class="w"> </span><span class="s2">"example.com"</span><span class="w">

</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Generated UPN: </span><span class="nv">$uniqueUPN</span><span class="s2">"</span><span class="w">
</span></code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>By using <strong>Test-UPNExist</strong> and <strong>Get-UniqueUPN</strong>, you can automate the process of generating unique UPNs for users in your Active Directory environment.</p>

<p>These functions ensure that each UPN is unique and adhere to the naming conventions you specify.</p>

<p>This approach minimizes the risk of conflicts and simplifies user management.</p>

<p>As mentioned both functions are still under development and I need to cleanup code and optimize some parts of it but the core functionality is there.</p>

<p>As usual all the code is available on my <a href="https://github.com/PsCustomObject/PowerShell-Functions">GitHub repository</a>.</p>

<p>I would love to receive feedback, ideas or implemntation ideas for them!</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="Howto" /><category term="PowerShell" /><category term="Tips" /><category term="HowTo" /><summary type="html"><![CDATA[In this short post I will share two new cmdlets I have developed to generate forest wide unique UPN handling the different edge cases.]]></summary></entry><entry><title type="html">PowerShell - Get day of weekday</title><link href="https://pscustomobject.github.io/powershell/howto/PowerShell-Get-Day-Of-Week-Number/" rel="alternate" type="text/html" title="PowerShell - Get day of weekday" /><published>2022-02-21T00:00:00+00:00</published><updated>2022-02-21T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/howto/PowerShell-Get-Day-Of-Week-Number</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/howto/PowerShell-Get-Day-Of-Week-Number/"><![CDATA[<h2 id="powershell-get-current-date">PowerShell Get current date</h2>

<p>PowerShell natively supports getting the current, or specific, date via the following cmdlet:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Date</span><span class="w">
</span></code></pre></div></div>

<p>Which, by default, will produce the following output:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Monday</span><span class="p">,</span><span class="w"> </span><span class="nx">February</span><span class="w"> </span><span class="nx">21</span><span class="p">,</span><span class="w"> </span><span class="nx">2022</span><span class="w"> </span><span class="nx">10:11:39</span><span class="w"> </span><span class="nx">PM</span><span class="w">
</span></code></pre></div></div>

<p>Cmdlet supports methods which can be used to display/return the day of the week for the selected date</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">DayOfWeek</span><span class="w">
</span></code></pre></div></div>

<p>There are situations where rather than returning a string with the day name it can be useful to return the <strong>number</strong> (1 to 7) associated with the day’s name, this can easily be accomplished with the following command</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">DayOfWeek</span><span class="o">.</span><span class="nf">value__</span><span class="w">

</span><span class="c"># Output</span><span class="w">
</span><span class="mi">1</span><span class="w">
</span></code></pre></div></div>

<p>This is specially handy when inserting data into SQL datbase supporting dates as <em>TinyInt</em> data types.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="Howto" /><category term="PowerShell" /><category term="Tips" /><category term="HowTo" /><summary type="html"><![CDATA[In this short post we will see how we can use PowerShell to get an integer representing the number associated with the day of the week for the current date.]]></summary></entry><entry><title type="html">Exchange Online Management Module - Could not use the certificate for signing</title><link href="https://pscustomobject.github.io/powershell/exchange/office%20365/Could-not-use-the-certificate-for-signing/" rel="alternate" type="text/html" title="Exchange Online Management Module - Could not use the certificate for signing" /><published>2022-01-27T00:00:00+00:00</published><updated>2022-01-27T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/exchange/office%20365/Could-not-use-the-certificate-for-signing</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/exchange/office%20365/Could-not-use-the-certificate-for-signing/"><![CDATA[<h2 id="exchange-online-certificate-based-authentication">Exchange Online Certificate Based authentication</h2>

<p>As I have written in my <a href="https://pscustomobject.github.io/powershell/exchange/office%20365/Cannot-bind-argument-to-parameter-Token-Expiry/">previous post about TokenExpiry error message Microsoft is retiring ability to connect to Exchange Online via basic authentication</a>.</p>

<p>You can read my article on how to implement <em>Certificate Based authentication</em> for Exchange Online <a href="https://pscustomobject.github.io/powershell/office365/exchange/Exchange-Online-Certificate-Based-Authentication/">here</a>.</p>

<h2 id="could-not-use-the-certificate-for-signing-error-message">Could not use the certificate for signing error message</h2>

<p>Today while I was updating code for one of our automations I created a request for a new certificate to use for authentication purposes.</p>

<p>Once I deployed code to our test environment automation was failing the connection to Exchange Online with the following error</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">System.Management.Automation.RuntimeException</span><span class="p">]</span><span class="w"> </span><span class="n">One</span><span class="w"> </span><span class="nx">or</span><span class="w"> </span><span class="nx">more</span><span class="w"> </span><span class="nx">errors</span><span class="w"> </span><span class="nx">occurred.</span><span class="w">
</span><span class="p">[</span><span class="n">Microsoft.Identity.Client.MsalClientException</span><span class="p">]</span><span class="w"> </span><span class="n">Could</span><span class="w"> </span><span class="nx">not</span><span class="w"> </span><span class="nx">use</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">for</span><span class="w"> </span><span class="nx">signing.</span><span class="w"> </span><span class="nx">See</span><span class="w"> </span><span class="nx">inner</span><span class="w"> </span><span class="nx">exception</span><span class="w"> </span><span class="nx">for</span><span class="w"> </span><span class="nx">details.</span><span class="w"> </span><span class="nx">Possible</span><span class="w"> </span><span class="nx">cause:</span><span class="w"> </span><span class="nx">this</span><span class="w"> </span><span class="nx">may</span><span class="w"> </span><span class="nx">be</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">known</span><span class="w"> </span><span class="nx">issue</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">apps</span><span class="w"> </span><span class="nx">build</span><span class="w"> </span><span class="nx">against</span><span class="w"> </span><span class="o">.</span><span class="nf">NET</span><span class="w"> </span><span class="nx">Desktop</span><span class="w"> </span><span class="nx">4.6</span><span class="w"> </span><span class="nx">or</span><span class="w"> </span><span class="nx">lower.</span><span class="w"> </span><span class="nx">Either</span><span class="w"> </span><span class="nx">target</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">higher</span><span class="w"> </span><span class="nx">version</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="o">.</span><span class="nf">NET</span><span class="w"> </span><span class="nx">desktop</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">4.6.1</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">above</span><span class="p">,</span><span class="w"> </span><span class="nx">or</span><span class="w"> </span><span class="nx">use</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">different</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">type</span><span class="w"> </span><span class="p">(</span><span class="n">non-CNG</span><span class="p">)</span><span class="w"> </span><span class="n">or</span><span class="w"> </span><span class="nx">sign</span><span class="w"> </span><span class="nx">your</span><span class="w"> </span><span class="nx">own</span><span class="w"> </span><span class="nx">assertion</span><span class="w"> </span><span class="nx">as</span><span class="w"> </span><span class="nx">described</span><span class="w"> </span><span class="nx">at</span><span class="w"> </span><span class="nx">aka.ms/msal-net-signed-assertion.</span><span class="w">
</span><span class="p">[</span><span class="n">System.Security.Cryptography.CryptographicException</span><span class="p">]</span><span class="w"> </span><span class="n">Invalid</span><span class="w"> </span><span class="nx">provider</span><span class="w"> </span><span class="nx">type</span><span class="w"> </span><span class="nx">specified.</span><span class="w">
</span></code></pre></div></div>

<p>Funnily enough the same certificate and cmdlets were working fine with PowerShell 7.</p>

<p>After quite some troubleshooting I’ve found out the problem was caused by the certificate’s private key using <a href="https://docs.microsoft.com/en-us/mem/configmgr/core/plan-design/network/cng-certificates-overview"><em>Cryptography Next Generate (CNG)</em></a> template rather than RSA.</p>

<p>Not having direct access to the CA releasing the certificate I could not change this so I had to resort on either running the automation in PowerShell 7 or update the certificate itself.</p>

<p>Luckily this is easily done via OpenSSL. Let’s see how.</p>

<h2 id="convert-certificate-private-key-from-cng-to-rsa">Convert Certificate private key from CNG to RSA</h2>

<p>If you have installed Git, cygwin or Windows Subsystem for Linux you just need to fire a bash prompt and use the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Extract the public key from the cert </span>
OpenSSL pkcs12 <span class="nt">-in</span> <span class="s2">"CNGCertificate.pfx"</span> <span class="nt">-nokeys</span> <span class="nt">-out</span> <span class="s2">"temp.cer"</span>

<span class="c"># Extract the private key</span>
OpenSSL pkcs12 <span class="nt">-in</span> <span class="s2">"CNGCertificate.pfx"</span> <span class="nt">-nocerts</span> <span class="nt">-out</span> <span class="s2">"temp.pem"</span>

<span class="c"># Convert key to RSA</span>
OpenSSL rsa <span class="nt">-inform</span> PEM <span class="nt">-in</span> <span class="s2">"temp.pem"</span> <span class="nt">-out</span> <span class="s2">"temp.rsa"</span>

<span class="c"># Finally create a new pfx file</span>
OpenSSL pkcs12 <span class="nt">-export</span> <span class="nt">-in</span> <span class="s2">"temp.cer"</span> <span class="nt">-inkey</span> <span class="s2">"temp.rsa"</span> <span class="nt">-out</span> <span class="s2">"RSACertificate.pfx"</span>
</code></pre></div></div>

<p class="notice--warning"><strong>Note:</strong> In the above commands I am not using a password for the certificate as everything is local to my machine but a password is definitely <em>required</em> when exporting a certificate together with the private key.</p>

<p>Once the new pfx file has been created all <em>temporary</em> certificates can be safely removed form the system and connection to Exchange Online will go through just fine.</p>

<p>Again if you can use PowerShell 7 you will not face this issue but in case you’re stuck with version 5.1 and facing this error message hopefully this post can save you some headaches.</p>

<p>Full credit for the solution goes to this <a href="https://stackoverflow.com/questions/22581811/invalid-provider-type-specified-cryptographicexception-when-trying-to-load-pri/34103154#34103154">StackOverFlow thread</a></p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="Exchange" /><category term="Office 365" /><category term="PowerShell" /><category term="Office365" /><category term="Exchange" /><summary type="html"><![CDATA[When trying to establish a connection to Exchange Online Could not use the certificate for signing error message is displayed.]]></summary></entry><entry><title type="html">Cannot bind argument to parameter TokenExpiryTime because it is null - Error Message</title><link href="https://pscustomobject.github.io/powershell/exchange/office%20365/Cannot-bind-argument-to-parameter-Token-Expiry/" rel="alternate" type="text/html" title="Cannot bind argument to parameter TokenExpiryTime because it is null - Error Message" /><published>2022-01-25T00:00:00+00:00</published><updated>2022-01-25T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/exchange/office%20365/Cannot%20bind%20argument%20to%20parameter-Token-Expiry</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/exchange/office%20365/Cannot-bind-argument-to-parameter-Token-Expiry/"><![CDATA[<h2 id="exchange-online-certificate-based-authentication">Exchange Online Certificate Based authentication</h2>

<p>Microsoft is, <em>finally</em>, disabling <strong>basic authentication</strong> (read username and password) in Exchange Online in favor of <strong>Certificate Based authentication</strong>.</p>

<p>Once this change is fully implemented, around mid February at least for some tenants, connecting via username and passwords to Exchange Online will not be possible anymore.</p>

<p>You can read my article on how to implement <em>Certificate Based authentication</em> for Exchange Online <a href="https://pscustomobject.github.io/powershell/office365/exchange/Exchange-Online-Certificate-Based-Authentication/">here</a>.</p>

<p>As a result of this change I started updating one of our automations, responsible for the whole life-cycle of our mailboxes, to ditch old credential objects in favor of the more secure Certificate Authentication.</p>

<p>This is when I encountered the <em>cannot bind argument to parameter ‘token expiry time’ because it is null.</em> error message.</p>

<h2 id="multiple-powershell-exchange-sessions">Multiple PowerShell Exchange Sessions</h2>

<p>When operating an hybrid environment it is pretty common to open, in the same window/session, a PowerShell connection to both Exchange on-prem and Exchange Online.</p>

<p>This is required as part of the configuration, usually creation of the mailbox, takes place in on-prem for example via the <em>New-RemoteMailbox</em> cmdlet while other parts of the configuration are performed directly online for example when delegating mailbox permissions.</p>

<p>While debugging my workflow I have noticed that, while trying to retreive mailbox information from the on-prem server, an exception was being thrown</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cmdlet I was running</span><span class="w">
</span><span class="n">Get-RemoteMailbox</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="nv">$userUpn</span><span class="w">

</span><span class="c"># Part of the exception message</span><span class="w">
</span><span class="n">Cannot</span><span class="w"> </span><span class="nx">bind</span><span class="w"> </span><span class="nx">argument</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="nx">parameter</span><span class="w"> </span><span class="s1">'token expiry time'</span><span class="w"> </span><span class="nx">because</span><span class="w"> </span><span class="nx">it</span><span class="w"> </span><span class="nx">is</span><span class="w"> </span><span class="nx">null.</span><span class="w">
</span></code></pre></div></div>

<p>It took me a bit to figure this out as no exception was thrown during the connection <em>phase</em> either nor there was any other obvious pointer.</p>

<p>When I was about to give up and open a ticket with Microsoft, which is usually as helpful as freezer in the North Pole, I discovered that establishing a connection to Exchange Online followed by a connection to the on-prem server was yielding the desired result. In my workflow I had this the other way around, first local Exchange and then Online service, which was causing the issue.</p>

<p class="notice--warning"><strong>Note:</strong> I have experienced/tested this with version 2.0.4 and 2.0.5 of Exchange Online PowerShell module but other versions could be affected as well.</p>

<p>I did not dig deep into the root cause of the issue but plan to do this tomorrow and already sent my feedback to exocmdletpreview {at} service {dot} microsoft {dot} com but I doubt I will hear anything from that channel. 
I will anyhow open a ticket with support to at least have an official statement/clarification on this.</p>

<p>As soon as I have any news I will update the post until then I hope you can find the information useful.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="Exchange" /><category term="Office 365" /><category term="PowerShell" /><category term="Office365" /><category term="Exchange" /><summary type="html"><![CDATA[When opening a PowerShell session to both on-prem and Exchange online in the same window cannot bing argument to parameter TokenExpiryTime because is null error message could be displayed. Let's explore how to solve this.]]></summary></entry><entry><title type="html">Setup an internal PowerShell Repository with NuGet Step by Step</title><link href="https://pscustomobject.github.io/powershell/howto/Setup-Internal-PowerShell-Repository/" rel="alternate" type="text/html" title="Setup an internal PowerShell Repository with NuGet Step by Step" /><published>2021-06-02T00:00:00+00:00</published><updated>2021-06-02T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/howto/Setup-Internal-PowerShell-Repository</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/howto/Setup-Internal-PowerShell-Repository/"><![CDATA[<h2 id="powershell-repositories">PowerShell repositories</h2>

<p>If you follow my blog you probably know my <a href="https://github.com/PsCustomObject">GitHub</a> where I share modules like the <a href="https://github.com/PsCustomObject/IT-ToolBox">IT-ToolBox</a> or handy standalone <a href="https://github.com/PsCustomObject/PowerShell-Functions">Functions</a> that I developed to solve specific issues.</p>

<p>In my posts I often talked about <a href="https://pscustomobject.github.io/powershell/tips/PowerShell-Restore-Default-Repository/">PowerShell Gallery</a> which you probably already used to install a module released by Microsoft or other developers.</p>

<p>Both are great tools when sharing code with a large audience over the internet, GitHub Private repositories are scope of this article, but a large chunk of my work involves developing <em>modules</em> that me and my colleagues user internally for automation purposes. These modules usually leverage technologies that are available only in my work environment and sometimes implement functions that would not have much sense outside my company.</p>

<p>One of the main issues we faced when initially started development of our own modules is ease of distribution to other team members for this reason we’ve setup internal <strong>NuGet</strong> repositories which can be used an <em>internal</em> PowerShell Gallery.</p>

<h2 id="required-tools">Required tools</h2>

<p>Here’s what you will need to follow this article and setup your own PowerShell repository:</p>

<ul>
  <li>A copy of <strong><a href="https://visualstudio.microsoft.com/downloads/">Visual Studio</a></strong> the community edition will do just fine and it’s free</li>
  <li>The ASP.NET and web development workload installed <a href="https://docs.microsoft.com/en-us/visualstudio/install/modify-visual-studio?view=vs-2019">here’s</a> a guide on how to modify an existing Visual Studio installation in case you don’t have the appropriate workload installed already</li>
  <li>A server running a supported copy of <strong>Windows Server</strong> I am using Windows 2019 but 2016 will do just fine</li>
</ul>

<p><strong>Note:</strong> If you do not have or cannot install Visual Studio at the end of the post I have posted a link to my <a href="https://github.com/PsCustomObject/Nuget-PowerShell-Repository/tree/main/NuGetRepository/NuGetRepository">GitHub Repository</a></p>

<h2 id="create-the-visual-studio-project">Create the Visual Studio Project</h2>

<p>Once all requirements are in place open Visual Studio and create a project using the <em>ASP.Web Application (Visual C#)</em> template.</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/VS_Create_New_Project.png">
  <img src="/assets/images/NugetArticle/VS_Create_New_Project.png" /></a>
</figure>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/VS_Web_Application.png">
  <img src="/assets/images/NugetArticle/VS_Web_Application.png" /></a>
</figure>

<p>Application name is not important just avoid <em>NuGet</em> as this would create a conflict with one of the <em>packages</em> we need to install as part of the dependencies, in my example I’ve used <strong>NuGetRepository</strong> but it can really be anything</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Application_Configuration.png">
  <img src="/assets/images/NugetArticle/Application_Configuration.png" /></a>
</figure>

<p>In the screen that will appear simply select the <em>Empty</em> template and then the <em>Create</em> button</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Project_Configuration.png">
  <img src="/assets/images/NugetArticle/Project_Configuration.png" /></a>
</figure>

<p>Once project has finished loading/being created <em>right-click</em> on the project name in <em>solution explorer</em> and select <strong>Manage NuGet Packages</strong></p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Manage_NuGet_Package.png">
  <img src="/assets/images/NugetArticle/Manage_NuGet_Package.png" /></a>
</figure>

<p>In the <em>NuGetRepository</em> window select the <em>Browse</em> tab and search for package <strong>NuGet.Server</strong> and click on the <strong>Install</strong> button.</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Install_NuGet_Component.png">
  <img src="/assets/images/NugetArticle/Install_NuGet_Component.png" /></a>
</figure>

<p>Keep I am using the latest stable release available at the time of this writing but you can select a different version of the package suiting your needs.</p>

<p>This step will take some time, depending on your connection speed, and a window showing you changes to the solution and asking your confirmation will appear in which case simply click on the <em>Ok</em> button.</p>

<p>Once the installation step is complete I highly recommend to change the build type from <em>Debug</em>, default for new projects, to <em>Release</em> as this will disable all debug logging that is otherwise enabled for the solution.</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Set_Build_Type.png">
  <img src="/assets/images/NugetArticle/Set_Build_Type.png" /></a>
</figure>

<p>Once this is done go to <em>Build / Build Solution</em> menu, or press F6 if you’re lazy like me, to <em>package</em> together all files making up the solution.</p>

<p>This will create a folder structure, under the path previously specified, containing all files required by the solution. Project path is visible in the <strong>Properties</strong> window under the <em>Project Folder</em> field</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Project_Path.png">
  <img src="/assets/images/NugetArticle/Project_Path.png" /></a>
</figure>

<p><strong>Note:</strong> By default solution will be build under the C:\Users\username\source\repos\AppName\AppName\ path</p>

<h2 id="install-required-server-components">Install required Server Components</h2>

<p>Once the solution has been build in Visual Studio move on the server that will be hosting the repository and launch the following cmdlet from an elevated PowerShell session to install required <em>IIS</em> components</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Splatted version</span><span class="w">
</span><span class="nv">$paramInstallWindowsFeature</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@{</span><span class="w">
    </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Web-Server'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Web-Net-Ext45'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Web-Asp-Net45'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Web-ISAPI-Filter'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Web-ISAPI-Ext'</span><span class="w">
    </span><span class="nx">IncludeManagementTools</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">Install-WindowsFeature</span><span class="w"> </span><span class="err">@</span><span class="nx">paramInstallWindowsFeature</span><span class="w">

</span><span class="c"># Long unsplatted version</span><span class="w">
</span><span class="n">Install-WindowsFeature</span><span class="w"> </span><span class="nx">Web-Server</span><span class="p">,</span><span class="nx">Web-Net-Ext45</span><span class="p">,</span><span class="nx">Web-Asp-Net45</span><span class="p">,</span><span class="nx">Web-ISAPI-Filter</span><span class="p">,</span><span class="nx">Web-ISAPI-Ext</span><span class="w"> </span><span class="nt">-IncludeManagementTools</span><span class="w">
</span></code></pre></div></div>

<p>Once all components are in place copy the solution files to a directory on the server with the default being <em>C:\inetpub\wwwroot</em>. Path is not really import and could be anything you’d like, I will just stick with the default.</p>

<p>As a reference here’s how the content of my application folder looks like on my test server</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/IIS_Application.png">
  <img src="/assets/images/NugetArticle/IIS_Application.png" /></a>
</figure>

<h3 id="update-webconfig-configuration-file">Update Web.Config configuration file</h3>

<p>The <strong>web.config</strong> file is located in the project root folder, assuming default path this will be <em>C:\inetpub\wwwroot\NuGetRepository</em>, open it with a text editor and under the <strong>&lt;system.web&gt;</strong> section you should see line similar the following (actual .Net Framework version could vary)</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;system.web&gt;</span>
    <span class="nt">&lt;compilation</span> <span class="na">debug=</span><span class="s">"true"</span> <span class="na">targetFramework=</span><span class="s">"4.6.1"</span> <span class="nt">/&gt;</span>
    <span class="c">&lt;!-- maxRequestLength is specified in Kb --&gt;</span>
    <span class="nt">&lt;httpRuntime</span> <span class="na">targetFramework=</span><span class="s">"4.6.1"</span> <span class="na">maxRequestLength=</span><span class="s">"30720"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;compilation</span> <span class="na">debug=</span><span class="s">"true"</span> <span class="na">targetFramework=</span><span class="s">"4.6"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/system.web&gt;</span>
</code></pre></div></div>

<p>Comment out or delete one of the instances of <strong>&lt;compilation&gt;</strong> tag so that resultant file will be similar the following</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;system.web&gt;</span>
    <span class="nt">&lt;compilation</span> <span class="na">debug=</span><span class="s">"true"</span> <span class="na">targetFramework=</span><span class="s">"4.6.1"</span> <span class="nt">/&gt;</span>
    <span class="c">&lt;!-- maxRequestLength is specified in Kb --&gt;</span>
    <span class="nt">&lt;httpRuntime</span> <span class="na">targetFramework=</span><span class="s">"4.6.1"</span> <span class="na">maxRequestLength=</span><span class="s">"30720"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/system.web&gt;</span>
</code></pre></div></div>

<p><strong>Note:</strong> If the <em>web.config</em> file is not updated when opening the IIS Application an <em>Internal Server Error</em> message will be displayed an application won’t work.</p>

<h2 id="deploy-application-to-iis">Deploy Application to IIS</h2>

<p>We can finally deploy the <em>NuGet</em> application to IIS to do so open **IIS Manager / Expand <Server Name=""> / Sites** right-click on Default Web Site and select **Add Application**</Server></p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Add_Application.png">
  <img src="/assets/images/NugetArticle/Add_Application.png" /></a>
</figure>

<p>In the window that appears provide a name (alias) for the application and specify the path where application files reside</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Application_Alias.png">
  <img src="/assets/images/NugetArticle/Application_Alias.png" /></a>
</figure>

<p>To test the configuration point your browser to <strong>http://localhost/Nuget</strong> which should yield the following page</p>

<figure>
  <a href="https://pscustomobject.github.io//assets/images/NugetArticle/Nuget_Page.png">
  <img src="/assets/images/NugetArticle/Nuget_Page.png" /></a>
</figure>

<h3 id="configure-package-directory">Configure Package Directory</h3>

<p>In the default configuration packages are stored under the same path where the application lives in the <em>Packages</em> directory, in our example <em>C:\inetpub\wwwroot\Nuget\Packages</em>. This can be changed in the <em>web.config</em> configuration file via the following tag</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c">&lt;!--
    Change the path to the packages folder. Default is ~/Packages.
    This can be a virtual or physical path.
    --&gt;</span>
    <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"packagesPath"</span> <span class="na">value=</span><span class="s">"C:\SomePath\Packages"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<h3 id="configure-repository-api-key">Configure Repository API Key</h3>

<p>Up to this point the repository is ready and can be used to <em>download</em> packages from it but if we need to <em>upload</em> data, in our example PowerShell modules, to it we would need some form of authentication which in NuGet is accomplished through an API key.</p>

<p>First of all generate a unique API key, I generally use a GUID generated via <em>New-Guid</em> cmdlet, open the <em>web.config</em> file and update the <strong>apiKey</strong> value as in the following example</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c">&lt;!--
    Determines if an Api Key is required to push\delete packages from the server.
    --&gt;</span>
    <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"requireApiKey"</span> <span class="na">value=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
    <span class="c">&lt;!--
    Set the value here to allow people to push/delete packages from the server.
    NOTE: This is a shared key (password) for all users.
    --&gt;</span>
    <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"apiKey"</span> <span class="na">value=</span><span class="s">"&lt;GUID VALUE&gt;"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>If you want to disable authentication all together you can simply set the <strong>requireApiKey</strong> to <em>false</em>.</p>

<h2 id="closing-notes">Closing notes</h2>

<p>This guide should set you in the right path for configuring your own internal PowerShell module repository but this is just the tip of the iceberg.</p>

<p>In the next articles I will explain how to register a repository and push modules to it.</p>

<h2 id="github-link">GitHub Link</h2>

<p>I have created a GitHub repository where I’ve posted both the package and the full Visual Studio Solution so you can simply download the files and be up and running in no time.</p>

<ul>
  <li><a href="https://github.com/PsCustomObject/Nuget-PowerShell-Repository/tree/main/NuGetRepository/NuGetRepository">This is the direct link to the application files</a> that you can simply copy over to IIS</li>
  <li><a href="https://github.com/PsCustomObject/Nuget-PowerShell-Repository">This is the link to the base repository</a> containing readme, changelog etc.</li>
</ul>

<p>I will keep this repository up to date when new versions of NuGet are released.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="HowTo" /><category term="PowerShell" /><category term="Tips" /><summary type="html"><![CDATA[In this article I will guide you through the creation, configuration and deployment of an internal NuGet repository that can be used to easily distribute and share PowerShell modules in the internal network.]]></summary></entry><entry><title type="html">Get current user UPN</title><link href="https://pscustomobject.github.io/powershell/tips/howto/Get-Current-User-UPN/" rel="alternate" type="text/html" title="Get current user UPN" /><published>2021-05-28T00:00:00+00:00</published><updated>2021-05-28T00:00:00+00:00</updated><id>https://pscustomobject.github.io/powershell/tips/howto/Get-Current-User-UPN</id><content type="html" xml:base="https://pscustomobject.github.io/powershell/tips/howto/Get-Current-User-UPN/"><![CDATA[<p>A few days ago while developing a cmdlet for an internal module in support to a larger automation workflow I found myself in need to easily derive <em>UserPrincipalName</em> of the currently logged on user running the command.</p>

<p>If you look  this up in your favorite search engine chances are you came across results like the following (there are more but these are the more common <em>suggestions</em> from users):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 1</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">UserName</span><span class="w">

</span><span class="c"># 2</span><span class="w">
</span><span class="p">[</span><span class="n">System.Security.Principal.WindowsIdentity</span><span class="p">]::</span><span class="n">GetCurrent</span><span class="p">()</span><span class="o">.</span><span class="nf">Name</span><span class="w">

</span><span class="c"># 3</span><span class="w">
</span><span class="p">[</span><span class="n">Environment</span><span class="p">]::</span><span class="n">UserName</span><span class="w">

</span><span class="c"># 4</span><span class="w">
</span><span class="err">$</span><span class="p">(</span><span class="n">Get-WMIObject</span><span class="w"> </span><span class="nt">-class</span><span class="w"> </span><span class="nx">Win32_ComputerSystem</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="nx">username</span><span class="p">)</span><span class="o">.</span><span class="nf">username</span><span class="w">
</span></code></pre></div></div>

<p>All the above methods are more or less equivalents and will yield the following results:</p>

<ol>
  <li>PsCustomObject  - Which is my samAccountName</li>
  <li>domain\PsCustomObject - Same as above just with the domain prefix</li>
  <li>PsCustomObject - Identical to number 1</li>
  <li>domain\PsCustomObject - Identical to number 2</li>
</ol>

<p>Issue is none of them will easily return the <strong>UserPrincipalName</strong> of the currently logged in user.</p>

<p>This can easily be achieved with the following command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">([</span><span class="n">ADSI</span><span class="p">]</span><span class="s2">"LDAP://&lt;SID=</span><span class="si">$(</span><span class="p">[</span><span class="n">System.Security.Principal.WindowsIdentity</span><span class="p">]::</span><span class="n">GetCurrent</span><span class="p">(</span><span class="si">)</span><span class="s2">.User.Value)&gt;"</span><span class="p">)</span><span class="o">.</span><span class="nf">UserPrincipalName</span><span class="w">
</span></code></pre></div></div>

<p>This command, despite not as user friendly as the other commands, will return the full UPN of the user for example <strong>PsCustomObject@domain.com</strong>.</p>

<p>I encourage you to explore the other available methods and properties of the <em>[System.Security.Principal.WindowsIdentity]</em> <a href="https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity?view=net-5.0">class</a> as it can be really handy when trying to get details about the current user.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="PowerShell" /><category term="Tips" /><category term="HowTo" /><category term="PowerShell" /><category term="Tips" /><summary type="html"><![CDATA[In this post we will explore how to easily retrieve currently logged in user UPN via PowerShell]]></summary></entry><entry><title type="html">Write RaspBerry image to SD card via command line</title><link href="https://pscustomobject.github.io/linux/azure/Write-Raspberry-Image-Command-Line/" rel="alternate" type="text/html" title="Write RaspBerry image to SD card via command line" /><published>2021-05-02T00:00:00+00:00</published><updated>2021-05-02T00:00:00+00:00</updated><id>https://pscustomobject.github.io/linux/azure/Write-Raspberry-Image-Command-Line</id><content type="html" xml:base="https://pscustomobject.github.io/linux/azure/Write-Raspberry-Image-Command-Line/"><![CDATA[<p>I am in the process of rebuilding my <em>Docker/Kubernetes</em> portable cluster which I build using a couple of RaspberryPi 4 and as part of this I needed to <em>reflash</em> the various SD cards where operating system for each node is installed.</p>

<p>Usually <a href="https://www.balena.io/etcher/">Balena Etcher</a> is my go-to tool for such endeavours but being in a rush and not easy way to download the tool on my Linux box I simply used the good old command line, here is how this is done.</p>

<p>First of all we need to locate the device mapped to our SD card, in my case I’m using a microSD to USB adapter, which can be done with the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>fdisk <span class="nt">-l</span>

Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
Disk model: WDC WD1003FZEX-0
Units: sectors of 1 <span class="k">*</span> 512 <span class="o">=</span> 512 bytes
Sector size <span class="o">(</span>logical/physical<span class="o">)</span>: 512 bytes / 4096 bytes
I/O size <span class="o">(</span>minimum/optimal<span class="o">)</span>: 4096 bytes / 4096 bytes
Disklabel <span class="nb">type</span>: dos
Disk identifier: 0x806d3748

Device     Boot      Start        End   Sectors   Size Id Type
/dev/sda1  <span class="k">*</span>          2048    1126399   1124352   549M  7 HPFS/NTFS/exFAT
/dev/sda3       1024002048 1953519615 929517568 443.2G  7 HPFS/NTFS/exFAT


Disk /dev/sdb: 465.76 GiB, 500107862016 bytes, 976773168 sectors
Disk model: Samsung SSD 850 
Units: sectors of 1 <span class="k">*</span> 512 <span class="o">=</span> 512 bytes
Sector size <span class="o">(</span>logical/physical<span class="o">)</span>: 512 bytes / 512 bytes
I/O size <span class="o">(</span>minimum/optimal<span class="o">)</span>: 512 bytes / 512 bytes
Disklabel <span class="nb">type</span>: dos
Disk identifier: 0xde0a016a

Device     Boot   Start       End   Sectors   Size Id Type
/dev/sdb1  <span class="k">*</span>       2048   2099199   2097152     1G 83 Linux
/dev/sdb2       2099200 976773119 974673920 464.8G 83 Linux

&lt;snip <span class="k">for </span>brevity&gt;

Device     Boot  Start     End Sectors  Size Id Type
/dev/sdd1  <span class="k">*</span>      2048  526335  524288  256M  c W95 FAT32 <span class="o">(</span>LBA<span class="o">)</span>
/dev/sdd2       526336 6819619 6293284    3G 83 Linux
</code></pre></div></div>

<p>In my case the SD card is mapped to device <strong>/dev/sdd</strong>, once we have found the device to use the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xzcat ./ubuntu-21.04-preinstalled-server-arm64+raspi.img.xz | <span class="nb">sudo dd </span><span class="nv">bs</span><span class="o">=</span>4M <span class="nv">of</span><span class="o">=</span>/dev/sdd <span class="nv">conv</span><span class="o">=</span>fsync
0+425226 records <span class="k">in
</span>0+425226 records out
3491662848 bytes <span class="o">(</span>3.5 GB, 3.3 GiB<span class="o">)</span> copied, 99.9634 s, 34.9 MB/s
</code></pre></div></div>

<p>In the above example I used <strong>Ubuntu Server 21.04</strong> as the operating system with the image file being stored in the same path where the command is being run but the same can be used with any other image file.</p>

<p>I hope this can be useful and probably in the future I will post how to easily build a portable Docker/Kubernetes cluster lab.</p>]]></content><author><name>PsCustomObject</name><email>PsCustomObject@outlook.com</email></author><category term="Linux" /><category term="Azure" /><category term="Linux" /><category term="Lab" /><summary type="html"><![CDATA[In this post we will explore how to use bash to write an OS image to an SD card for use with RaspBerryPI]]></summary></entry></feed>