<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Scripting for Systems Administration]]></title><description><![CDATA[Scripting for Systems Administration]]></description><link>https://sysdive.net</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 17:48:14 GMT</lastBuildDate><atom:link href="https://sysdive.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Open Active Directory Users and Computers for a specific OU]]></title><description><![CDATA[After using Active Directory Users and Computers (ADUC) for decades I only recently learned that there is indeed a way to 'directly jump' to a specific Organizational Unit (OU) when starting it. I wanted this for years because I tend to forget where ...]]></description><link>https://sysdive.net/open-active-directory-users-and-computers-specific-ou</link><guid isPermaLink="true">https://sysdive.net/open-active-directory-users-and-computers-specific-ou</guid><category><![CDATA[Active Directory]]></category><category><![CDATA[organizational unit]]></category><category><![CDATA[ADUC]]></category><category><![CDATA[dsa.msc]]></category><category><![CDATA[Shortcuts]]></category><dc:creator><![CDATA[Marcus Schommler]]></dc:creator><pubDate>Wed, 06 Sep 2023 08:32:46 GMT</pubDate><content:encoded><![CDATA[<p>After using Active Directory Users and Computers (ADUC) for decades I only recently learned that there is indeed a way to 'directly jump' to a specific Organizational Unit (OU) when starting it. I wanted this for years because I tend to forget where exactly some things are to be found in our rather big OU structure.</p>
<p>From the command line, ADUC can be called - quite 'self-explanatory' - by starting <code>dsa.msc</code>. Documentation about available command line options is hard to find but in addition to <code>/domain=</code> and <code>/server=</code> there is one option that you can use to specify the Relative Distinguished Name (RDN) for an OU to be used. Example:</p>
<pre><code class="lang-powershell">dsa.msc /rdn=<span class="hljs-string">"OU=VMs,OU=ESX,OU=KO,OU=Lokal,OU=...,OU=...,OU=..."</span>
</code></pre>
<p>RDNs are essentially the 'left part' of a full Distinguished Name (DN) attribute for an OU. They can be derived from the latter by simply removing the <code>DC=...</code> part from them.</p>
<p>When specifying an RDN on the command line ADUC will not simply 'jump to' this OU but will instead <strong>show only this OU</strong> (plus sub-OUs) and nothing much else (except stored queries). It doesn't even show the OU path from the root to the specified OU. This might be a drawback for daily work but I did create a handful of desktop shortcuts to directly get to specific and deeply nested OUs anyhow. And, of course, you can have an 'unrestricted' session of ADUC running in parallel.</p>
]]></content:encoded></item><item><title><![CDATA[Conditionally Redirecting Output from Inside a Powershell Script File]]></title><description><![CDATA[If you look at documentation and examples about output redirection in Powershell, you'll often find code showing you how to redirect e.g. Write-Warning on the command line level like shown here:
.\script.ps1 3> warnings.log

Usually, it's best practi...]]></description><link>https://sysdive.net/conditionally-redirecting-output-powershell-script</link><guid isPermaLink="true">https://sysdive.net/conditionally-redirecting-output-powershell-script</guid><category><![CDATA[Powershell]]></category><category><![CDATA[scheduled task]]></category><category><![CDATA[output redirection]]></category><category><![CDATA[write-warning]]></category><dc:creator><![CDATA[Marcus Schommler]]></dc:creator><pubDate>Fri, 18 Aug 2023 17:44:25 GMT</pubDate><content:encoded><![CDATA[<p>If you look at <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.2">documentation</a> and examples about output redirection in Powershell, you'll often find code showing you how to redirect e.g. <code>Write-Warning</code> on the command line level like shown here:</p>
<pre><code class="lang-powershell">.\script.ps1 <span class="hljs-number">3</span>&gt; warnings.log
</code></pre>
<p>Usually, it's best practice to let the caller of a script decide how to apply output redirection. But there might be cases where letting a script itself decide might have some appeal. One example: Some of my scripts are not only used interactively but also being run as scheduled tasks. To be able to check possible warnings from such a script run after its completion I wanted to redirect the output of <code>Write-Warning</code>. The straightforward way to do this is to create a scheduled task definition including the command line arguments necessary for output redirection:</p>
<pre><code class="lang-powershell">C:\Windows\System32\WindowsPowerShell\v1.<span class="hljs-number">0</span>\powershell.exe <span class="hljs-operator">-File</span> C:\scripts\<span class="hljs-built_in">get-allUserGroupMemberships</span>.ps1 <span class="hljs-number">3</span>&gt; warnings.log
</code></pre>
<p>But this scheduled definition will redirect the warnings to the same file on every execution, thus overwriting the data from the last run. Overwriting could easily be avoided by using <code>3&gt;&gt; warnings.log</code> to append to the now cumulative log file. But what if I wanted to have a separate log file for every (e.g. daily) execution of this script?</p>
<p>Following the documentation above and looking at the more advanced <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.3#example-3-send-success-warning-and-error-streams-to-a-file">examples</a> it became clear to me that output redirection in Powershell is not limited to passing command line arguments to a script being executed. Instead, every single code block or function call can be augmented with output redirection parameters.</p>
<p>Now how to apply output redirection on the code block or function call level to the use case I intended it for? A simple implementation would be a script with a parameter <code>$WarnRedirect</code>. If called without a value for this parameter (e.g. from a console session) the main body of the script would call its 'worker function' without redirection parameters. If there is a value for this parameter, output redirection will be applied:</p>
<pre><code class="lang-powershell"><span class="hljs-function">[<span class="hljs-type">CmdletBinding</span>()]</span>
<span class="hljs-keyword">param</span>(
  [<span class="hljs-built_in">string</span>] <span class="hljs-variable">$WarnRedirect</span> = <span class="hljs-string">""</span>
)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Write-A-Warning</span></span> {
    <span class="hljs-function">[<span class="hljs-type">CmdletBinding</span>()]</span>
    <span class="hljs-keyword">param</span>()
    <span class="hljs-built_in">Write-Warning</span> <span class="hljs-string">"`nWarning message <span class="hljs-variable">$</span>(Get-Date)`n"</span>
}

<span class="hljs-variable">$cmd</span> = <span class="hljs-string">"Write-A-Warning"</span>
<span class="hljs-keyword">if</span> (<span class="hljs-variable">$WarnRedirect</span>) {
        <span class="hljs-variable">$WarningLogFile</span> = <span class="hljs-string">"Warnings-"</span> + <span class="hljs-variable">$</span>(<span class="hljs-built_in">Get-Date</span> <span class="hljs-literal">-Format</span> <span class="hljs-string">"yyyyMMdd-HHmm"</span>) + <span class="hljs-string">".log"</span>
    <span class="hljs-variable">$cmd</span> += <span class="hljs-string">" 3&gt;<span class="hljs-variable">$WarningLogFile</span>"</span>
}
<span class="hljs-built_in">Invoke-Expression</span> <span class="hljs-variable">$cmd</span>
</code></pre>
<p>If you want to avoid the use of <code>Invoke-Expression</code> the last few script lines (its main code block) could be also written as follows:</p>
<pre><code class="lang-powershell"><span class="hljs-keyword">if</span> (<span class="hljs-variable">$WarnRedirect</span>) {
        <span class="hljs-variable">$WarningLogFile</span> = <span class="hljs-string">"Warnings-"</span> + <span class="hljs-variable">$</span>(<span class="hljs-built_in">Get-Date</span> <span class="hljs-literal">-Format</span> <span class="hljs-string">"yyyyMMdd-HHmm"</span>) + <span class="hljs-string">".log"</span>
        <span class="hljs-built_in">Write-A</span><span class="hljs-literal">-Warning</span> <span class="hljs-number">3</span>&gt;<span class="hljs-variable">$WarningLogFile</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">Write-A</span><span class="hljs-literal">-Warning</span>
}
</code></pre>
<p>Of course, there are other ways of achieving the same result of getting one warning file per scheduled script execution. One such implementation would be to create a parameterless 'wrapper script' and use this in the definition of the scheduled task. The wrapper script could then construct and execute a Powershell command line for the 'worker script' with the appropriate redirection arguments, including a date-dependent output file name. But my own tastes tend to try to avoid a proliferation of script files, thus opting for the approach presented here.</p>
]]></content:encoded></item><item><title><![CDATA[Using vSphere PowerCLI Get-Annotation: Low Performance and possible Mitigations]]></title><description><![CDATA[While working on a script used for compliance checking on vSphere virtual machines I noticed that getting annotations (custom attributes) from vSphere has quite a few peculiarities. One major disappointment was the very slow speed of the PowerCLI Cmd...]]></description><link>https://sysdive.net/using-vsphere-powercli-get-annotation-low-performance-and-possible-mitigations</link><guid isPermaLink="true">https://sysdive.net/using-vsphere-powercli-get-annotation-low-performance-and-possible-mitigations</guid><category><![CDATA[Powershell]]></category><category><![CDATA[vsphere]]></category><category><![CDATA[Get-Annotation]]></category><category><![CDATA[PowerCLI]]></category><dc:creator><![CDATA[Marcus Schommler]]></dc:creator><pubDate>Fri, 11 Aug 2023 17:01:20 GMT</pubDate><content:encoded><![CDATA[<p>While working on a script used for compliance checking on vSphere virtual machines I noticed that getting annotations (custom attributes) from vSphere has quite a few peculiarities. One major disappointment was the very slow speed of the <a target="_blank" href="https://developer.vmware.com/web/tool/13.1.0/vmware-powercli">PowerCLI</a> Cmdlet <a target="_blank" href="https://developer.vmware.com/docs/powercli/latest/vmware.vimautomation.core/commands/get-annotation/#Default">Get-Annotation</a>. In the following, I will provide details and show possible mitigations.</p>
<p>Just to be clear: the ultra-slow execution of Get-Annotation might not be a problem at all in your environment. If you just run a script once in a while to get annotations for a handful of virtual machines there is no need to further look into this subject. But the consequences of its low performance might become more of a nuisance if the number of VMs and assigned custom attributes are higher and you have to run scripts retrieving and evaluating them quite often. For example: In our vSphere V7 environment we are running 200+ VMs and use a handful of custom attributes to manage them. Our compliance checking script initially ran for more than six minutes to get and process information from vSphere, Active Directory, DNS, and DHCP. When I was tasked with extending this script, its very long running time was seriously slowing down my development cycle of coding and testing. The rather astonishing result of adding some profiling code was that it spent most of its time calling Get-Annotation and waiting for results. Therefore I began an investigation into the details.</p>
<h2 id="heading-getting-information-about-all-vms-and-all-annotations-for-them">Getting information about all VMs and all annotations for them</h2>
<p>For starters, let's get some basic information on all virtual machines and measure how long this will take:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$vms</span> = <span class="hljs-built_in">get-vm</span>}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 56.4338
#&gt;</span>
<span class="hljs-variable">$vms</span>.Count
<span class="hljs-comment">&lt;# sample output:
231
#&gt;</span>
</code></pre>
<p>Now let's get all annotations for all virtual machines gathered with the previous run of <code>Get-VM</code>:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span>)}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 5184.1585
#&gt;</span>
</code></pre>
<p>Executing this is about twenty times slower than getting quite a bit of other information about all VMs. While getting annotations this way is a lot slower it still doesn't sound too bad to worry about it. But then this kind of 'bulk get' is the fastest way I found to get annotations. Your own script code might do much worse - as mine initially did.</p>
<h2 id="heading-filter-left-is-a-bad-idea-here">'Filter left' is a bad idea here</h2>
<p>One common optimization strategy for Powershell pipelines is 'filter left': If a Cmdlet supports some kind of filtering it is usually faster to use this instead of applying <code>where-object</code> to its results further down in your pipeline. While <code>Get-Annotation</code> has no <code>-Filter</code> parameter it does have <code>-CustomAttribute</code> which allows you to specify the custom attributes you are interested in. Let's see how this performs by specifying just two attributes instead of all to be fetched for our VMs (for execution please substitute attributes you are using in your environment):</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> <span class="hljs-literal">-CustomAttribute</span> (<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>))}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 24509.2726
#&gt;</span>
</code></pre>
<p>That's quite impressive in a negative way: Asking vSphere to get you only two attributes instead of all takes almost five times longer. Let's see how this scales by asking <code>Get-Annotation</code> for more custom attributes, first three of them:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> <span class="hljs-literal">-CustomAttribute</span> (<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>, <span class="hljs-string">"Backup Status"</span>))}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 32414.4898
#&gt;</span>
</code></pre>
<p>Now repeat with asking for four attributes:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> <span class="hljs-literal">-CustomAttribute</span> (<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>, <span class="hljs-string">"Backup Status"</span>, <span class="hljs-string">"Archived"</span>))}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 39692.3221
#&gt;</span>
</code></pre>
<p>Execution time seems to increase in an almost linear fashion with the number of attributes specified with <code>-CustomAttribute</code>. While this can make you wonder how inefficiently custom attributes are implemented in vSphere the only pragmatic solution (for now) is to stay away from explicitly asking for specific attributes when using <code>Get-Annotation</code> and do some handling/filtering of them on your own.</p>
<h2 id="heading-how-long-does-where-object-take-in-this-case">How long does <code>Where-Object</code> take in this case?</h2>
<p>For performance reasons, we are now back to fetching all annotations for all virtual machines. But if we only need a few of them we might want to apply <code>Where-Object</code> after fetching them. Shown here is the code and results for filtering two to four attributes:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> | <span class="hljs-built_in">where-object</span> {(<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>) <span class="hljs-operator">-contains</span> <span class="hljs-variable">$_</span>.Name})}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 5066.0718
#&gt;</span>

<span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> | <span class="hljs-built_in">where-object</span> {(<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>, <span class="hljs-string">"Backup Status"</span>) <span class="hljs-operator">-contains</span> <span class="hljs-variable">$_</span>.Name})}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 5137.3431
#&gt;</span>

<span class="hljs-built_in">Measure-Command</span> {<span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> | <span class="hljs-built_in">where-object</span> {(<span class="hljs-string">"ManagedBy"</span>, <span class="hljs-string">"DeletionDue"</span>, <span class="hljs-string">"Backup Status"</span>, <span class="hljs-string">"Archived"</span>) <span class="hljs-operator">-contains</span> <span class="hljs-variable">$_</span>.Name})}
<span class="hljs-comment">&lt;# sample output:
...
TotalMilliseconds : 5087.164
#&gt;</span>
</code></pre>
<p>That's an interesting observation: When using <code>Where-Object</code> the execution time does not differ noticeably if more attributes are to be included in the output. This is in stark contrast to the behavior shown for <code>Get-Annotation</code> in conjunction with <code>-CustomAttribute</code>.</p>
<h2 id="heading-getting-rid-of-empty-strings">Getting rid of empty strings</h2>
<p>Another peculiar thing about vSphere annotations is that defining a custom attribute for virtual machines results in all of them getting a value for it, even if you don't explicitly set one. If you list the contents of the collection <code>$ann</code> you might see many lines where the value of a custom attribute simply is an empty string. Thus your collection of annotations can contain quite a lot of entries you might not need. To get rid of these entries add a filter expressing as follows:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> { 
    <span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> | <span class="hljs-built_in">where-object</span> {<span class="hljs-variable">$_</span>.Value <span class="hljs-operator">-ne</span> <span class="hljs-string">""</span>} )
}
<span class="hljs-comment">&lt;# sample output:
... 
TotalMilliseconds : 5141.478
#&gt;</span>
</code></pre>
<p>Getting all annotations and filtering the empty ones is only about a second slower than not filtering empty strings.</p>
<p>In the next step, I wanted to simplify the result collection by reducing the first member of every entry to the name of the VM. Right now this is an 'AnnotatedEntity' object with several members - which I don't have a use for. Here I ran into another unpleasant surprise: For unknown reasons the following statement with its additional <code>Select-Object</code> pipeline step now ran almost four times slower.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> { 
    <span class="hljs-variable">$ann</span> = (<span class="hljs-variable">$vms</span> | <span class="hljs-built_in">get-annotation</span> | <span class="hljs-built_in">where-object</span> {<span class="hljs-variable">$_</span>.Value <span class="hljs-operator">-ne</span> <span class="hljs-string">""</span>} 
    | <span class="hljs-built_in">Select-Object</span> <span class="hljs-selector-tag">@</span>{Name=<span class="hljs-string">"VMName"</span>;Expression={<span class="hljs-variable">$_</span>.AnnotatedEntity.Name}}, Name, Value)
}
<span class="hljs-comment">&lt;# output:
...
TotalMilliseconds : 19323.4264
#&gt;</span>
</code></pre>
<p>I didn't care into investigating this further and decided to omit this additional pipeline step for my purposes.</p>
<h2 id="heading-getting-the-annotations-for-a-specific-virtual-machine">Getting the annotations for a specific virtual machine</h2>
<p>So far this post has shown that iterating over a lot of virtual machines and retrieving annotations by name for each of them can be very time-consuming. Also, it became clear that 'bulk getting' and filtering afterward is much more efficient. Up until here, we have a collection of annotations for all VMs containing only these having a value other than an empty string. But you still might have to iterate over all VMs in your code and access the annotations for each VM inside this loop. How to do this efficiently? The collection <code>$ann</code> is a simple list where every entry is an object having three members for the 'annotated entity' (virtual machine), attribute name, and attribute value. The easiest way to get at the attributes for a specific VM would be to filter the collection with <code>Where-Object</code> for the name of the VM in question. Here this is done in a loop over all existing VMs (circa 230):</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> { 
    <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$vm</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$vms</span>) {
        <span class="hljs-variable">$vm_annotations</span> = (<span class="hljs-variable">$ann</span> | <span class="hljs-built_in">where-object</span> {<span class="hljs-variable">$_</span>.AnnotatedEntity.Name <span class="hljs-operator">-eq</span> <span class="hljs-variable">$vm</span>.Name}) 
    }
}
<span class="hljs-comment">&lt;# output:
...
TotalMilliseconds : 964.0608
#&gt;</span>
</code></pre>
<p>This level of performance can be OK for a small number of iterations and a few custom attributes to be handled. But to satisfy my curiosity I tried if this could be done faster. I tried to achieve this by copying the original 'flat' collection into a nested hash table where the keys for the outer table are the names of the virtual machines and each entry in this outer table is itself another hash table for attribute names and values for a specific VM. This might sound complicated but can be done with a few lines of code. Also, this preparatory step doesn't need much time:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> {
    <span class="hljs-variable">$processedAnnotations</span> = <span class="hljs-selector-tag">@</span>{}
    <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$a</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$ann</span>) {
        <span class="hljs-variable">$vmName</span> = <span class="hljs-variable">$a</span>.AnnotatedEntity.Name
        <span class="hljs-keyword">if</span> (<span class="hljs-operator">-not</span> <span class="hljs-variable">$processedAnnotations</span>.ContainsKey(<span class="hljs-variable">$vmName</span>)) {
            <span class="hljs-variable">$processedAnnotations</span>[<span class="hljs-variable">$vmName</span>] = <span class="hljs-selector-tag">@</span>{}
        }
        <span class="hljs-variable">$processedAnnotations</span>[<span class="hljs-variable">$vmName</span>].Add(<span class="hljs-variable">$a</span>.Name, <span class="hljs-variable">$a</span>.Value)
    }
}
<span class="hljs-comment">&lt;# output:
...
TotalMilliseconds : 15.7992
#&gt;</span>
</code></pre>
<p>Now does using a nested hash table lead to better performance?</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Measure-Command</span> { 
    <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$vm</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$vms</span>) {
        <span class="hljs-variable">$vm_annotations</span> = <span class="hljs-variable">$processedAnnotations</span>[<span class="hljs-variable">$vm</span><span class="hljs-type">.Name</span>]
    }
}
<span class="hljs-comment">&lt;# output:
...
TotalMilliseconds : 8.6309
#&gt;</span>
</code></pre>
<p>Quite impressive: This is about a hundred times faster than the <code>Where-Object</code> way of getting the annotations for a single VM.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This is a blog entry that I hope will become obsolete in the future. I guess that it would be not too difficult for VMWare to fix the bad performance for retrieval of custom attributes experience when using <code>Get-Attribute</code>. But then I don't think that fixing performance issues for rather specialized cases like this one would rank high on any development to-do list. So maybe some hints about how to optimize the performance of handling vSphere annotations might be helpful for some people for still a while.</p>
]]></content:encoded></item></channel></rss>