<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PowerShell | DBA von Nebenan</title>
	<atom:link href="https://dbavonnebenan.de/category/powershell/feed/" rel="self" type="application/rss+xml" />
	<link>https://dbavonnebenan.de</link>
	<description>Sql Server, Performance &#38; PowerShell Automation</description>
	<lastBuildDate>Fri, 06 Feb 2026 11:50:38 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://dbavonnebenan.de/wp-content/uploads/2025/04/cropped-www_logo-32x32.png</url>
	<title>PowerShell | DBA von Nebenan</title>
	<link>https://dbavonnebenan.de</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Automate SQL Server HA: AD, SPN, and Cluster Setup</title>
		<link>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/</link>
					<comments>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Fri, 06 Feb 2026 11:47:28 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[AlwaysOn]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[GMSA]]></category>
		<category><![CDATA[SPN]]></category>
		<category><![CDATA[WSFC]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=669</guid>

					<description><![CDATA[<p>Production-Ready Windows Cluster Deployment: Automation of Failover Cluster, gMSA, Kerberos and local rights via PowerShell.</p>
<p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/">Automate SQL Server HA: AD, SPN, and Cluster Setup</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 6px; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-11670a24-effc-43d1-9b6f-f1421b3d4c12">
<p class="has-open-sauce-sans-font-family" style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">What awaits you here?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list has-open-sauce-sans-font-family">
<li><a href="#WSFC" title="">Windows Server Failover Cluster Deployment with Extended AD and DNS Permissions</a></li>



<li><a href="#GMSA" title="">Identity Management: Group Managed Service Accounts (gMSA)</a></li>



<li><a href="#KERBEROS" title="">Kerberos and Service Principal Names (SPN)</a></li>



<li><a href="#LSP" title="">Local Security Policies</a></li>



<li>Background knowledge on why configurations are done the way they are</li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="0-intro" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Who hasn&#8217;t been there? Before you can even start installing SQL Server, you&#8217;ve either repetitively clicked through pages of bad GUIs or, if available, juggled a loose collection of scripts to semi-automatically set up all dependencies and best practices.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:90%">
<p>Even worse is the case when, as a consultant, you don&#8217;t have Domain Admin access (which is how it should be) and you have to guide the customer — who may have never done this before — via Teams &amp; Co. If this isn&#8217;t already a planned installation with knowledge transfer, something like this creates pain. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92d.png" alt="🤭" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<div class="wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:10%">
<figure class="wp-block-image aligncenter size-full is-resized"><img fetchpriority="high" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="PAIN Emote" class="wp-image-516" style="aspect-ratio:0.8024665981500514;width:65px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="(max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<p>Accordingly, I&#8217;ve now gathered all my loose scripts and developed a function for each topic area that automates all these tasks and logs them for archival purposes. Thanks also to Claude for the support. Nobody reviews and documents code better than Claude. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60b.png" alt="😋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Now you might ask: Aren&#8217;t there dbatools and DSC? Sure, I like using those too. But transparent, traceable scripts without framework overhead convey knowledge better — especially when aspiring DBAs lack the big-picture perspective. Operating SQL Server starts at the bare metal, goes through storage, Windows Server, Active Directory, and ends at a Certificate Authority. And for critical infrastructure companies or small mid-sized businesses with 3 clusters, I don&#8217;t want to start a fundamental discussion about dbatools or DSC right from the beginning.</p>



<p>Let&#8217;s start with a production-ready Windows Server Failover Cluster — including CNO/VCO permissions, DNS registration, and quorum configuration. Everything repeatable, everything logged, everything traceable.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="1-das-lab-szenario" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The Lab Scenario</h2>



<p style="margin-bottom:var(--wp--preset--spacing--40)">To make the steps tangible, the following environment and naming conventions are used</p>



<ul class="wp-block-list">
<li><strong>Nodes</strong>
<ul class="wp-block-list">
<li><code>LAB-NODE01</code> (192.168.100.12)</li>



<li><code>LAB-NODE02</code> (192.168.100.13)</li>
</ul>
</li>



<li><strong>Cluster Name</strong>
<ul class="wp-block-list">
<li><code>MSSQL-CL-01</code> (192.168.100.14)</li>
</ul>
</li>



<li><strong>SQL Instance</strong>
<ul class="wp-block-list">
<li><code>LAB22A</code></li>
</ul>
</li>



<li><strong>Domain</strong>
<ul class="wp-block-list">
<li><code>lab.local</code></li>
</ul>
</li>



<li><strong>Service Accounts</strong> Group Managed Service Accounts (gMSA)
<ul class="wp-block-list">
<li><code>g-LAB22A-oltp</code></li>



<li><code>g-LAB22A-agnt</code></li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"/>



<h2 class="wp-block-heading" id="WSFC" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Windows Server Failover Cluster Deployment </h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1"><code>cluster_manageSQLCluster.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.md" target="_blank" rel="noopener" title="cluster_manageSQLCluster.md">cluster_manageSQLCluster.md</a></code></p>
</div>
</div>
</div>
</div>



<div style="height:14px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-align-left">The WSFC forms the foundation for AlwaysOn Availability Groups. The script manages the entire lifecycle from feature installation to witness configuration.</p>



<h3 class="wp-block-heading has-text-align-left" id="3-feature-installation-amp-cluster-erstellung" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Feature Installation &amp; Cluster Creation</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">First, the <code>Failover-Clustering</code> feature is installed on the individual nodes and the cluster is created. An important aspect of automation is the Cluster Name Object (CNO): It needs write permissions in DNS and Active Directory to be able to automatically create listener records later. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; auto-links: false; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation InstallFeature `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -LogPath &quot;C:\Temp\Cluster_Feature_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p class="has-text-align-left" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Afterwards, the cluster is created. The script additionally sets the corresponding permissions in AD and DNS.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation CreateCluster `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -StaticIP &quot;192.168.100.14&quot; `
    -DnsDomain &quot;lab.local&quot; `
    -TargetOU &quot;OU=lab_computer,DC=lab,DC=local&quot; `
    -LogPath &quot;C:\Temp\Cluster_Create_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="4-file-share-witness-quorum" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">File Share Witness (Quorum)</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">For a 2-node cluster, a witness is mandatory to avoid split-brain scenarios. Here, a file share on the domain controller (<code>\\LAB-DC\quorum$</code>) is used. </p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Manual Intermediate Step</strong><br>The Cluster Computer Object (<code>MSSQL-CL-01$</code>) requires <strong>Full Control</strong> at both the share and NTFS permission level of the witness path (<code>\\LAB-DC\quorum$</code>). This must be ensured before running the following command.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation SetWitness `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -WitnessShare &quot;\\LAB-DC\quorum$&quot; `
    -LogPath &quot;C:\Temp\Cluster_Witness_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The script primarily uses the <a href="https://learn.microsoft.com/en-us/powershell/module/failoverclusters/" target="_blank" rel="noopener" title="FailoverClusters"><code>FailoverClusters</code></a> module</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>New-Cluster</code>
<ul class="wp-block-list">
<li>Creates the cluster, adds the nodes, and sets the management IP</li>
</ul>
</li>



<li><code>Set-ClusterQuorum</code>
<ul class="wp-block-list">
<li>Configures the witness mode (here: File Share Witness)</li>
</ul>
</li>



<li><code>Install-WindowsFeature</code>
<ul class="wp-block-list">
<li>Installs the RSAT tools and the cluster service</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="GMSA" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Identity Management: gMSA</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.ps1" target="_blank" rel="noopener" title="security_createGMSA.ps1"><code>security_createGMSA.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.md" target="_blank" rel="noopener" title="security_createGMSA.md"><code>security_createGMSA.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Using standard users as service accounts can lead to expired passwords and downtime. The recommended method for SQL Server is <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview" target="_blank" rel="noopener" title="gMSA"><code>Group Managed Service Accounts (gMSA)</code></a>. Here, the Key Distribution Service (KDS) of Active Directory handles password rotation; nobody needs to know the password.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">KDS Root Key</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Before the first gMSA can be created, a KDS Root Key must exist once per forest. If a key already exists, the creation will be skipped.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -InitializeKDS `
    -LogPath &quot;C:\Temp\KDS_Init.log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">gMSA Creation</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Instead of assigning servers directly to a gMSA, it is best practice to create a security group (<code>SQL_LAB_CL01</code>) that contains the computer accounts of the cluster nodes. In addition to setting up the actual gMSA, this script can also create the security groups and handle the assignments. And always keep the descriptions of AD objects well-maintained. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -AccountName @( `
        @(&quot;g-LAB22A-oltp&quot;, &quot;SQL Server Engine Account for LAB22A&quot;), `
        @(&quot;g-LAB22A-agnt&quot;, &quot;SQL Server Agent Account for LAB22A&quot;) ) `
    -SecurityGroupName &quot;SQL_LAB_CL01&quot; `
    -SecurityGroupDescription &quot;SQL Server Cluster CL01 gMSA Group&quot; `
    -ServerNames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -CreateSecurityGroup `
    -SecurityGroupOU &quot;OU=lab_secgrp,DC=lab,DC=local&quot; `
    -PasswordIntervalDays 90 `
    -LogPath &quot;C:\Temp\gMSA_Creation_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">After this step, a restart of the nodes is required for the new group membership to take effect.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">Local Registration</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">After creation, the gMSAs must be registered locally on the nodes. The following script can be used for this purpose.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Required for local setup (Local Admin)
Add-WindowsFeature -Name RSAT-AD-PowerShell

$gMSAs = @(
    &#039;g-LAB22A-oltp&#039;,
    &#039;g-LAB22A-agnt&#039;
)

foreach ($gMSA in $gMSAs) {

    if (Test-ADServiceAccount -Identity $gMSA -ErrorAction SilentlyContinue) {
        try {
            Install-ADServiceAccount -Identity $gMSA -ErrorAction Stop
            Write-Host &quot;$gMSA installed&quot; -ForegroundColor Green
        } catch {
            Write-Host &quot;$gMSA - Installation error: $($_.Exception.Message)&quot; -ForegroundColor Red
        }
    } else {
        Write-Host &quot;$gMSA does not exist&quot; -ForegroundColor Yellow
    }
}
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">After this step, a restart of the nodes is required for the new group membership to take effect.</p>



<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">The script primarily uses the <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title="ActiveDirectory"><code>ActiveDirectory</code></a> module</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>Add-KdsRootKey</code>
<ul class="wp-block-list">
<li>Creates the root key for the Group Key Distribution Service</li>
</ul>
</li>



<li><code>New-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Creates the gMSA object in AD</li>
</ul>
</li>



<li><code>Set-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Configures which computers (PrincipalsAllowedToRetrieveManagedPassword) are allowed to read the password</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="KERBEROS" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Kerberos and Service Principal Names (SPN)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1">security_setSqlSpn.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" target="_blank" rel="noopener" title="">security_setSqlSpn</a></code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" title=""><code>.</code>md</a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">A gMSA does not have the right to write its own SPNs in AD by default. When the SQL service starts, the registration often fails, leading to NTLM fallbacks. Even though I prefer to set SPNs in a controlled and manual manner, the automatic registration must be technically possible to avoid fallbacks.</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)">The script solves this problem by granting the gMSA the rights in AD to manage its own SPNs, in addition to creating the SPNs. It sets ACLs on the nodes for <code>Read</code>, <code>Write</code>, and <code>Validated write</code> on the <code>servicePrincipalName</code> attribute.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_setSqlSpn.ps1 `
    -ServiceAccount &quot;LAB\g-LAB22A-oltp&quot; `
    -InstanceName &quot;LAB22A&quot; `
    -Port 51101 `
    -Hostnames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -Domain &quot;lab.local&quot; `
    -IsGMSA `
    -SetADPermissions `
    -LogPath &quot;C:\Temp\SPN_AlwaysOn_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">On one hand, <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setspn" target="_blank" rel="noopener" title="setspn.exe"><code>setspn.exe</code></a> is used for SPN management, and on the other hand, the <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title=""><code>ActiveDirectory</code></a> module is used again to verify accounts and extract SIDs.</p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Since none of the modules can manipulate Active Directory permissions, the .NET Framework <a href="https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices?view=windowsdesktop-10.0" target="_blank" rel="noopener" title="System.DirectoryServices"><code>System.DirectoryServices</code></a> is used directly for this purpose, which provides various classes to completely turn AD inside out. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="LSP" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Local Security Policies</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.ps1" target="_blank" rel="noopener" title="security_setSqlLocalSec.ps1">security_setSqlLocalSec.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.md" target="_blank" rel="noopener" title=""><code>security_setSqlLocalSec.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">As everywhere in IT, SQL Server should also follow the <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener" title="PoLP"><code>PoLP</code></a>, which is why no service account gets local administrator privileges. For SQL Server to still run flawlessly and performantly, the service account needs specific rights in the operating system (&#8222;User Rights Assignment&#8220;). Anyone who has done this manually a few times will be very happy about this solution. This must be executed locally on the respective node.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# ===== Set Engine Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-oltp$&quot; `
        -ServiceType &quot;engine&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Engine_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;

# ===== Set Agent Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-agnt$&quot; `
        -ServiceType &quot;agent&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Agent_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Microsoft Cmdlets Used</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Since PowerShell has no native cmdlets for editing local security policies, the Windows tool <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/secedit" target="_blank" rel="noopener" title="secedit.exe"><code>secedit.exe</code></a> is used and its export is manipulated.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Summary</h2>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">With these four building blocks, the foundation for a SQL Server installation has been laid. It wasn&#8217;t just an installation that was performed, but a valid, documented configuration that considers identity (gMSA), network (DNS/SPN), and OS performance. In the next step, the actual SQL Server instance can be installed.</p>



<p>Also check out the linked MD files — they contain background information on best practices and further articles from Microsoft</p>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="good to know emote" class="wp-image-345" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="(max-width: 307px) 100vw, 307px" /></figure><p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/">Automate SQL Server HA: AD, SPN, and Cluster Setup</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SQL Server HA: AD, SPN und Cluster Setup automatisieren</title>
		<link>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/</link>
					<comments>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Fri, 06 Feb 2026 11:20:00 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[AD]]></category>
		<category><![CDATA[AlwaysOn]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[GMSA]]></category>
		<category><![CDATA[SPN]]></category>
		<category><![CDATA[WSFC]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=641</guid>

					<description><![CDATA[<p>Production-Ready Windows Cluster Deployment: Automatisierung von Failover Cluster, gMSA, Kerberos und lokalen Rechten via PowerShell.</p>
<p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/">SQL Server HA: AD, SPN und Cluster Setup automatisieren</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<div style="border: 1px dotted #eb2b14; border-radius: 6px; background-color: #eeeeee; padding-top: var(--wp--preset--spacing--30); padding-left: var(--wp--preset--spacing--30); padding-right: var(--wp--preset--spacing--30); padding-bottom: var(--wp--preset--spacing--30); " class="ub-styled-box ub-bordered-box wp-block-ub-styled-box" id="ub-styled-box-8bf7c8f5-ed36-44d1-82b0-6849bd1507a5">
<p class="has-open-sauce-sans-font-family" style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" id="ub-styled-box-bordered-content-">Was erwartet dich hier?</p>



<ul style="margin-top:0;margin-right:0;margin-bottom:0;margin-left:0" class="wp-block-list has-open-sauce-sans-font-family">
<li><a href="#WSFC" title="">Windows Server Failover Cluster Deployment mit erweiterten AD- und DNS-Berechtigungen</a></li>



<li><a href="#GMSA" title="">Identity Management: Group Managed Service Accounts (gMSA)</a></li>



<li><a href="#KERBEROS" title="">Kerberos und Service Principal Names (SPN)</a></li>



<li><a href="#LSP" title="">Lokale Sicherheitsrichtlinien (Local Security Policies)</a></li>



<li>Hintergrundwissen warum Konfigurationen gemacht werden wie sie gemacht werden</li>
</ul>


</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/DBAScriptBox


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="0-intro" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Wer kennt es nicht? Bevor man überhaupt mit der Installation von SQL Server loslegen kann, hat man sich entweder repetitiv durch seitenweise schlechte GUIs geklickt oder wenn verfügbar, mit einer losen Sammlung an Scripten jongliert und semiautomatisch alle Abhängigkeiten und Best Practices eingerichtet.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>Noch schlimmer ist der Fall, wenn man als Berater gar keinen Domain Admin hat (was auch so sein sollte) und man den Kunden, der dies vielleicht noch nie gemacht hat, per Teams &amp; Co anleiten muss. Wenn dies nicht eh eine geplante Installation mit Know-how-Transfer ist, erzeugt so etwas Schmerz. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92d.png" alt="🤭" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="PAIN Emote" class="wp-image-516" style="aspect-ratio:0.8024665981500514;width:75px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="(max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<p>Dementsprechend habe ich nun mal all meine losen Scripte zusammengewürfelt und für jeden Themenbereich eine recht umfangreiche Funktion entwickelt, welche all diese Tasks automatisiert und für Archivzwecke loggt. Danke auch an Claude für die Unterstützung. Keiner kontrolliert und dokumentiert Code besser als Claude. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f60b.png" alt="😋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>Nun könnte man sich fragen: Es gibt doch dbatools und DSC? Klar, nutze ich auch gerne. Aber transparente, nachvollziehbare Scripts ohne Framework-Overhead vermitteln Wissen besser &#8211; gerade wenn angehenden DBAs der Blick fürs Ganze fehlt. Betrieb von SQL Server fängt auf dem Blech an, geht über Storage, Windows Server, Active Directory und hört bei einer Certificate Authority auf. Und bei KRITIS-Unternehmen oder kleinen Mittelständlern mit 3 Clustern will ich zu Beginn keine Grundsatzdiskussion über dbatools oder DSC führen.</p>



<p>Los geht&#8217;s mit einem Production-Ready Windows Server Failover Cluster &#8211; inklusive CNO/VCO-Berechtigungen, DNS-Registrierung und Quorum-Konfiguration. Alles wiederholbar, alles geloggt, alles nachvollziehbar.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" id="1-das-lab-szenario" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Lab Szenario</h2>



<p style="margin-bottom:var(--wp--preset--spacing--40)">Um die Schritte greifbar zu machen, wird folgende Umgebung und Naming-Conventions genutzt</p>



<ul class="wp-block-list">
<li><strong>Nodes</strong>
<ul class="wp-block-list">
<li><code>LAB-NODE01</code> (192.168.100.12)</li>



<li><code>LAB-NODE02</code> (192.168.100.13)</li>
</ul>
</li>



<li><strong>Cluster Name</strong>
<ul class="wp-block-list">
<li><code>MSSQL-CL-01</code> (192.168.100.14)</li>
</ul>
</li>



<li><strong>SQL Instanz</strong>
<ul class="wp-block-list">
<li><code>LAB22A</code></li>
</ul>
</li>



<li><strong>Domain</strong>
<ul class="wp-block-list">
<li><code>lab.local</code></li>
</ul>
</li>



<li><strong>Service Accounts</strong> Group Managed Service Accounts (gMSA)
<ul class="wp-block-list">
<li><code>g-LAB22A-oltp</code></li>



<li><code>g-LAB22A-agnt</code></li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"/>



<h2 class="wp-block-heading" id="WSFC" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Windows Server Failover Cluster Deployment </h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1"><code>cluster_manageSQLCluster.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/system/cluster_manageSQLCluster.md" target="_blank" rel="noopener" title="cluster_manageSQLCluster.md">cluster_manageSQLCluster.md</a></code></p>
</div>
</div>
</div>
</div>



<div style="height:14px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-text-align-left">Der WSFC bildet das Fundament für AlwaysOn Availability Groups. Das Script steuert den gesamten Lifecycle von der Feature-Installation bis zur Witness-Konfiguration.</p>



<h3 class="wp-block-heading has-text-align-left" id="3-feature-installation-amp-cluster-erstellung" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Feature Installation &amp; Cluster Erstellung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Zunächst wird das Feature <code>Failover-Clustering</code> auf den einzelnen Nodes installiert und der Cluster erstellt. Ein wichtiger Aspekt bei der Automatisierung ist das Cluster-Objekt (CNO): Es benötigt Schreibrechte im DNS und Active Directory um später automatisiert Listener-Records anlegen zu können. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; auto-links: false; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation InstallFeature `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -LogPath &quot;C:\Temp\Cluster_Feature_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p class="has-text-align-left" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Anschließend wird der Cluster erstellt. Das Script setzt zusätzlich die entsprechenden Berechtigungen in AD und DNS.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation CreateCluster `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -Nodes &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -StaticIP &quot;192.168.100.14&quot; `
    -DnsDomain &quot;lab.local&quot; `
    -TargetOU &quot;OU=lab_computer,DC=lab,DC=local&quot; `
    -LogPath &quot;C:\Temp\Cluster_Create_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="4-file-share-witness-quorum" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">File Share Witness (Quorum)</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Für einen 2-Node Cluster ist ein Witness zwingend erforderlich, um Split-Brain-Szenarien zu vermeiden. Hier wird ein File Share auf dem Domain Controller (<code>\\LAB-DC\quorum$</code>) genutzt. </p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>Manueller Zwischenschritt</strong><br>Das Cluster-Computer-Objekt (<code>MSSQL-CL-01$</code>) benötigt <strong>Full Control</strong> auf Ebene der Share- und NTFS-Berechtigungen des Witness-Pfads (<code>\\LAB-DC\quorum$</code>). Dies muss vor dem folgenden Befehl sichergestellt sein.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\cluster_manageSQLCluster.ps1 `
    -Operation SetWitness `
    -ClusterName &quot;MSSQL-CL-01&quot; `
    -WitnessShare &quot;\\LAB-DC\quorum$&quot; `
    -LogPath &quot;C:\Temp\Cluster_Witness_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Script nutzt primär das <a href="https://learn.microsoft.com/en-us/powershell/module/failoverclusters/" target="_blank" rel="noopener" title="FailoverClusters"><code>FailoverClusters</code></a> Modul</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>New-Cluster</code>
<ul class="wp-block-list">
<li>Erstellt den Cluster, bindet die Nodes ein und setzt die Management-IP</li>
</ul>
</li>



<li><code>Set-ClusterQuorum</code>
<ul class="wp-block-list">
<li>Konfiguriert den Witness-Modus (hier: File Share Witness)</li>
</ul>
</li>



<li><code>Install-WindowsFeature</code>
<ul class="wp-block-list">
<li>Installiert die RSAT-Tools und den Cluster-Dienst</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="GMSA" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Identity Management: gMSA</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.ps1" target="_blank" rel="noopener" title="security_createGMSA.ps1"><code>security_createGMSA.ps1</code></a></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_createGMSA.md" target="_blank" rel="noopener" title="security_createGMSA.md"><code>security_createGMSA.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Die Nutzung von Standard-Usern als Service Accounts kann zu abgelaufenen Passwörtern und Downtime führen. Die empfohlene Methode für SQL Server sind <a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview" target="_blank" rel="noopener" title="gMSA"><code>Group Managed Service Accounts (gMSA)</code></a>. Hierbei übernimmt der Key Distribution Service (KDS) des Active Directory die Passwortrotation; Niemand muss das Passwort kennen.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">KDS Root Key</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Bevor der erste gMSA erstellt werden kann, muss einmalig pro Forest ein KDS Root Key existieren. Sollte bereits ein Key existieren, wird die Erstellung übersprungen.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -InitializeKDS `
    -LogPath &quot;C:\Temp\KDS_Init.log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">gMSA Erstellung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Anstatt Server direkt einem gMSA zuzuordnen, ist es Best Practice, eine Security Group zu erstellen (<code>SQL_LAB_CL01</code>), welche die Computerkonten der Cluster-Nodes enthält. Neben der Einrichtung der eigentlichen gMSA kann dieses Script auch die Security Gruppen erstellen und die Zuordnungen vornehmen. Und immer schön die Beschreibungen bei AD-Objekten pflegen. </p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_createGMSA.ps1 `
    -AccountName @( `
        @(&quot;g-LAB22A-oltp&quot;, &quot;SQL Server Engine Account for LAB22A&quot;), `
        @(&quot;g-LAB22A-agnt&quot;, &quot;SQL Server Agent Account for LAB22A&quot;) ) `
    -SecurityGroupName &quot;SQL_LAB_CL01&quot; `
    -SecurityGroupDescription &quot;SQL Server Cluster CL01 gMSA Group&quot; `
    -ServerNames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -CreateSecurityGroup `
    -SecurityGroupOU &quot;OU=lab_secgrp,DC=lab,DC=local&quot; `
    -PasswordIntervalDays 90 `
    -LogPath &quot;C:\Temp\gMSA_Creation_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Nach diesem Schritt ist ein Neustart der Nodes erforderlich, damit die neue Gruppenmitgliedschaft wirksam wird.</p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">Lokale Registrierung</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Nach der Erstellung müssen die gMSA lokal auf den Nodes registriert werden. Hierzu kann folgendes  Script genutzt werden.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Wichtig für lokale Einrichtung ( Local Admin )
Add-WindowsFeature -Name RSAT-AD-PowerShell

$gMSAs = @(
    &#039;g-LAB22A-oltp&#039;,
    &#039;g-LAB22A-agnt&#039;
)

foreach ($gMSA in $gMSAs) {

    if (Test-ADServiceAccount -Identity $gMSA -ErrorAction SilentlyContinue) {
        try {
            Install-ADServiceAccount -Identity $gMSA -ErrorAction Stop
            Write-Host &quot;$gMSA installiert&quot; -ForegroundColor Green
        } catch {
            Write-Host &quot;$gMSA - Fehler bei Installation: $($_.Exception.Message)&quot; -ForegroundColor Red
        }
    } else {
        Write-Host &quot;$gMSA existiert nicht&quot; -ForegroundColor Yellow
    }
}
</pre></div>


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Nach diesem Schritt ist ein Neustart der Nodes erforderlich, damit die neue Gruppenmitgliedschaft (TGT) wirksam wird.</p>



<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Das Script nutzt primär das <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title="ActiveDirectory"><code>ActiveDirectory</code></a> Modul</p>



<ul style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)" class="wp-block-list">
<li><code>Add-KdsRootKey</code>
<ul class="wp-block-list">
<li>Erzeugt den Root Key für den Group Key Distribution Service</li>
</ul>
</li>



<li><code>New-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Erstellt das gMSA Objekt im AD</li>
</ul>
</li>



<li><code>Set-ADServiceAccount</code>
<ul class="wp-block-list">
<li>Konfiguriert, welche Computer (PrincipalsAllowedToRetrieveManagedPassword) das Passwort lesen dürfen</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="KERBEROS" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Kerberos und Service Principal Names (SPN)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.ps1" target="_blank" rel="noopener" title="cluster_manageSQLCluster.ps1">security_setSqlSpn.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" target="_blank" rel="noopener" title="">security_setSqlSpn</a></code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlSpn.md" title=""><code>.</code>md</a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Ein gMSA besitzt standardmäßig nicht das Recht, seine eigenen SPNs im AD zu schreiben. Startet der SQL Dienst, schlägt die Registrierung oft fehl, was zu NTLM-Fallbacks führt. Auch wenn ich SPNs bevorzugt kontrolliert und manuell setze, muss die automatische Registrierung technisch möglich sein, um Fallbacks zu vermeiden.</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)">Das Script löst dieses Problem, indem es zusätzlich zur SPN Erstellung dem gMSA die Rechte im AD gewährt, seine eigenen SPNs zu verwalten. Es setzt ACLs der Nodes für <code>Read</code>, <code>Write</code> und <code>Validated write</code> auf das Attribut <code>servicePrincipalName</code>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
.\security_setSqlSpn.ps1 `
    -ServiceAccount &quot;LAB\g-LAB22A-oltp&quot; `
    -InstanceName &quot;LAB22A&quot; `
    -Port 51101 `
    -Hostnames &quot;LAB-NODE01&quot;,&quot;LAB-NODE02&quot; `
    -Domain &quot;lab.local&quot; `
    -IsGMSA `
    -SetADPermissions `
    -LogPath &quot;C:\Temp\SPN_AlwaysOn_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" id="5-verwendete-microsoft-cmdlets" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Zum einen wird <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setspn" target="_blank" rel="noopener" title="setspn.exe"><code>setspn.exe</code></a> für Verwaltung der SPN genutzt, zum anderen wieder das <a href="https://learn.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2025-ps" target="_blank" rel="noopener" title=""><code>ActiveDirectory</code></a> Modul, um Accounts zu verifizieren und die SIDs zu extrahieren.</p>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Da keines der Module Active Directory Permsission manipulieren kann, wird hierfür direkt das .NET Framework <a href="https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices?view=windowsdesktop-10.0" target="_blank" rel="noopener" title="System.DirectoryServices"><code>System.DirectoryServices</code></a> genutzt, welches diverse Klassen bereitstellt, um das AD komplett auf Links zu drehen. </p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" id="LSP" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Lokale Sicherheitsrichtlinien (Local Security Policies)</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-c901c877 wp-block-columns-is-layout-flex" style="border-width:1px;border-top-left-radius:2px;border-top-right-radius:2px;border-bottom-left-radius:2px;border-bottom-right-radius:2px;margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--80);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--80)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2692.png" alt="⚒" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><code><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.ps1" target="_blank" rel="noopener" title="security_setSqlLocalSec.ps1">security_setSqlLocalSec.ps1</a></code></p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p class="has-text-align-center"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /><br><a href="https://github.com/gabrielkoehl/DBAScriptBox/blob/main/windows/security/security_setSqlLocalSec.md" target="_blank" rel="noopener" title=""><code>security_setSqlLocalSec.md</code></a></p>
</div>
</div>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Wie überall in der IT sollte auch beim Sql Server nach dem <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener" title="PoLP"><code>PoLP</code></a> gearbeitet werden, weshalb kein Service Account den lokalen Administrator bekommt.Damit SQL Server trotzdem fehlerfrei und performant läuft, benötigt der Service Account spezifische Rechte im Betriebssystem (&#8222;User Rights Assignment&#8220;). Wer dies ein paar mal manuell gemacht hat, wird sich sehr über diese Lösung freuen. Diese muss lokal auf dem jeweiligen Node ausgeführt werden.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# ===== Set Engine Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-oltp$&quot; `
        -ServiceType &quot;engine&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Engine_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;

# ===== Set Agent Permissions for gMSA =====

    .\security_setSqlLocalSec.ps1 `
        -ServiceAccount &quot;g-LAB22A-agnt$&quot; `
        -ServiceType &quot;agent&quot; `
        -IsGMSA `
        -LogPath &quot;C:\Temp\LAB22A_Agent_$(Get-Date -Format &#039;yyyyMMdd_HHmmss&#039;).log&quot;
</pre></div>


<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Verwendete Microsoft Cmdlets</h3>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Da PowerShell keine nativen Cmdlets besitzt, um lokale Sicherheitsrichtlinien (LSA) zu bearbeiten, wird das Windows-Tool <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/secedit" target="_blank" rel="noopener" title="secedit.exe"><code>secedit.exe</code></a> genutzt und der Export manipuliert.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Summary</h2>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Mit diesen vier Bausteinen wurde das Fundament für eine Sql Server Installation gelegt. Es wurde nicht nur eine Installation durchgeführt, sondern eine valide, dokumentierte Konfiguration geschaffen, die Identität (gMSA), Netzwerk (DNS/SPN) und OS-Performance (LSA) berücksichtigt. Im nächsten Schritt kann die eigentliche SQL Server Instanz installiert werden.</p>



<p>Schaut euch auch die verlinkten MD-Files an, dort stehen Hintergrundinfos zu den Best Practices und weiterführende Artikel von Microsoft</p>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="good to know emote" class="wp-image-345" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure><p>The post <a href="https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/">SQL Server HA: AD, SPN und Cluster Setup automatisieren</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/automate-sql-server-ha-ad-spn-and-cluster-setup_de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>psTerminalPerfCounter – SpeedUp your Performance Counters in Terminal</title>
		<link>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_en/</link>
					<comments>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Sat, 26 Jul 2025 16:07:18 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[counter]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<category><![CDATA[TERMINAL]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=618</guid>

					<description><![CDATA[<p>Visualize Windows Performance Counters directly in terminal with psTerminalPerfCounter. Language-independent templates, maximum customization. Perfect for quick AdHoc monitoring.</p>
<p>The post <a href="https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_en/">psTerminalPerfCounter – SpeedUp your Performance Counters in Terminal</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Current Release ( 2025-10-11 )</h2>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>


https://www.powershellgallery.com/packages/psTerminalPerfCounter/0.2.0


<ul class="wp-block-list">
<li><a href="https://github.com/gabrielkoehl/psTerminalPerfCounter/blob/main/CHANGELOG.md" target="_blank" rel="noopener" title="Changelog ">Changelog</a></li>



<li><a href="https://github.com/gabrielkoehl/psTerminalPerfCounter/tree/main?tab=readme-ov-file#documentation" target="_blank" rel="noopener" title="Documentation">Documentation</a></li>
</ul>



<div style="height:27px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Windows Performance Counters are absolutely powerful, but when I need something quick or monitoring tailored to a specific problem, I get frustrated fast. Usually I fall back on other options &#8211; PowerShell native Get-Counter, CIM, or existing monitoring solutions at my clients. But that&#8217;s never truly ad-hoc either, and I&#8217;ve accepted it.</p>



<p>Recently I had the chance to join Uwe Ricken&#8217;s Performance Tuning Workshop at DataSaturday, and he clearly named Performance Counters as a must-have in every DBA&#8217;s toolkit. What I didn&#8217;t know until then: SQL Server creates individual counters like <strong>Compile/sec</strong>, <strong>AdHoc Queries/sec</strong>, etc.</p>



<figure class="wp-block-image aligncenter size-full is-resized is-style-default" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img loading="lazy" decoding="async" width="500" height="54" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing.png" alt="MINDBLOWING" class="wp-image-608" style="width:500px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing.png 500w, https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing-300x32.png 300w" sizes="auto, (max-width: 500px) 100vw, 500px" /></figure>



<p>The possibilities to visualize dependencies directly &#8211; endless. But how long should I sit there configuring that in Windows Performance Monitor, especially ad-hoc at a client site?</p>



<p>Uwe showed the capabilities in Windows Admin Center, which enables templates you can use flexibly across servers. For systems or environments where Admin Center is used strategically, definitely a great choice.<br>But two problems remain: As a consultant, I can&#8217;t install software at every client, and if Admin Center happens to be running, my English templates fail on German systems.</p>



<p>Still, I was so incredibly hooked by the template idea and the SQL Server possibilities that 4 weeks of after-work crunchtime were spinning in my head. So let me introduce my first module in the PowerShell Gallery:</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)"><strong>psTerminalPerfCounter 0.1.0-preview</strong> A PowerShell module for displaying real-time graphs of Windows Performance Counters directly in the terminal console. This module provides an easy way to visualize system performance metrics without requiring external graphing tools or GUI applications by using templates and multilanguage support.</p>


https://www.powershellgallery.com/packages/psTerminalPerfCounter/0.1.0-preview


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">You&#8217;ll find all technical details and development status in the repository &#8211; I just want to show the different possibilities here.</p>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Main Features</h2>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">100% PowerShell</h3>



<ul class="wp-block-list">
<li>no external dependencies</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Terminal Graphics</h3>



<ul class="wp-block-list">
<li>obviously it&#8217;s not a real engine, the more counters the choppier it gets, but it&#8217;s sufficient</li>



<li>The foundation comes from <a href="https://github.com/PrateekKumarSingh/Graphical" target="_blank" rel="noopener" title="">Prateek Singh</a> solution. Without finding this, I would have had to solve this problem first, for which I probably wouldn&#8217;t have had time. THANKS!!!</li>
</ul>



<figure class="wp-block-image aligncenter size-full is-resized" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40)"><img loading="lazy" decoding="async" width="632" height="757" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu.png" alt="" class="wp-image-609" style="width:383px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu.png 632w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu-250x300.png 250w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Configuration Templates</h3>



<ul class="wp-block-list">
<li>every output element is controlled via configuration files</li>



<li>flexible, portable</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Data Output Customization</h3>



<ul class="wp-block-list">
<li>each counter in the configuration file can use its own output
<ul class="wp-block-list">
<li>Table, Graph, or Both</li>
</ul>
</li>



<li>arbitrary axis scaling in steps and max values</li>



<li>arbitrary value coloring based on user-defined ColorMap per counter</li>



<li>value conversion via configuration file (Byte -&gt; KB -&gt; MB -&gt; GB)</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Language Independent</h3>



<ul class="wp-block-list">
<li>configuration files are based on counter IDs, not paths 
<ul class="wp-block-list">
<li>portable, regardless of system language translation from ID to counter path happens at runtime </li>



<li>no external dependencies</li>
</ul>
</li>



<li>I had no idea this was possible with Windows built-in tools. Here too I stumbled across a blog at <a href="https://powershell.one/tricks/performance/performance-counters" target="_blank" rel="noopener" title="">PowerShell.ONE</a> by chance. Again, a big THANKS!!! My original solution had multiple language variants (DE, EN, FR, ES) which would be set by reading system locale.</li>
</ul>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Demo with CPU Configuration</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Install module with required dependency (GripDevJsonSchemaValidator)
Install-Module -Name psTerminalPerfCounter -AllowPrerelease

# Import the module
Import-Module psTerminalPerfCounter

# Start Default Cpu Configuration
Start-tpcMonitor -ConfigName CPU
</pre></div>


<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-51c0b34d wp-block-columns-is-layout-flex" style="padding-top:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="552" height="110" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load.png" alt="" class="wp-image-610" style="width:622px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load.png 552w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load-300x60.png 300w" sizes="auto, (max-width: 552px) 100vw, 552px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>When loading the configuration, counter IDs are translated to counter paths and tested</p>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-0884d4d2 wp-block-columns-is-layout-flex" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="613" height="155" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample.png" alt="" class="wp-image-611" style="width:629px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample.png 613w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample-300x76.png 300w" sizes="auto, (max-width: 613px) 100vw, 613px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>If successful, the first samples are collected so a graph can be displayed</p>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-e242d015 wp-block-columns-is-layout-flex" style="padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="767" height="774" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run.png" alt="" class="wp-image-612" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run.png 767w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run-297x300.png 297w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run-150x150.png 150w" sizes="auto, (max-width: 767px) 100vw, 767px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>Thats IT! </p>



<p>Within 10 seconds, 2 Performance Counters running, no installation, no language issues.</p>
</div>
</div>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Demo Counter Translation</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Translate ID -&gt; Path
Get-tpcPerformanceCounterInfo 238-6
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="711" height="148" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id.png" alt="" class="wp-image-613" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id.png 711w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id-300x62.png 300w" sizes="auto, (max-width: 711px) 100vw, 711px" /></figure>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Search Counter -&gt; Including ID
Get-tpcPerformanceCounterInfo &quot;Queue&quot;
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0"><img loading="lazy" decoding="async" width="1013" height="600" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1.png" alt="" class="wp-image-614" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1.png 1013w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1-300x178.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1-768x455.png 768w" sizes="auto, (max-width: 1013px) 100vw, 1013px" /></figure>



<p class="has-contrast-color has-text-color has-link-color wp-elements-dedb5f3a2a3a9c6525f87d44e77731d7" style="margin-bottom:var(--wp--preset--spacing--50)"><em>Output is heavily truncated since the ResultSet is enormous</em></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Search Counter -&gt; Including ID
Get-tpcPerformanceCounterInfo &quot;Processor Queue&quot;
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="675" height="79" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2.png" alt="" class="wp-image-615" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2.png 675w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2-300x35.png 300w" sizes="auto, (max-width: 675px) 100vw, 675px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>What&#8217;s next?</strong></h2>



<p>The foundation works, the planned feature list is long, and what I really need for SQL Server is still far off. I hope I haven&#8217;t solved a problem that doesn&#8217;t exist, but this module simply addresses all the issues I&#8217;ve always had with Performance Counters, as mentioned above.</p>



<p>Furthermore, this is my first larger project that I&#8217;m developing publicly, so GitHub might not always meet expected standards. I&#8217;m always happy about feedback and suggestions.</p>



<figure class="wp-block-image aligncenter size-full is-resized" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="happy" class="wp-image-330" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>


https://github.com/gabrielkoehl/psTerminalPerfCounter<p>The post <a href="https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_en/">psTerminalPerfCounter – SpeedUp your Performance Counters in Terminal</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>psTerminalPerfCounter &#8211; SpeedUp your Performance Counters in Terminal</title>
		<link>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_de/</link>
					<comments>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Sat, 26 Jul 2025 15:53:33 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[CONSOLE]]></category>
		<category><![CDATA[counter]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[PERFORMANCE]]></category>
		<category><![CDATA[TERMINAL]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=605</guid>

					<description><![CDATA[<p>Visualisiere Windows Performance Counter direkt im Terminal mit psTerminalPerfCounter. Sprachunabhängige Templates, maximale Anpassbarkeit. Perfekt für schnelles AdHoc-Monitoring.</p>
<p>The post <a href="https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_de/">psTerminalPerfCounter – SpeedUp your Performance Counters in Terminal</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Current Release ( 2025-10-11 )</h2>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>


https://www.powershellgallery.com/packages/psTerminalPerfCounter/0.2.0


<ul class="wp-block-list">
<li><a href="https://github.com/gabrielkoehl/psTerminalPerfCounter/blob/main/CHANGELOG.md" target="_blank" rel="noopener" title="Changelog ">Changelog</a></li>



<li><a href="https://github.com/gabrielkoehl/psTerminalPerfCounter/tree/main?tab=readme-ov-file#documentation" target="_blank" rel="noopener" title="Documentation">Documentation</a></li>
</ul>



<div style="height:13px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Die Windows Performance Counter sind absolut mächtig, aber wenn es mal schnell gehen muss oder ich genau passend zu einem Problem ein Monitoring benötige, bin ich schnell genervt. Meistens nutze ich dann doch andere Möglichkeiten, PowerShell Native Get-Counter, CIM oder greife auf vorhandene Monitoring-Lösungen bei meinen Kunden zurück. Aber auch das geht nie AdHoc aber ich habe es akzeptiert.</p>



<p>Nun habe ich beim letzten DataSaturday Uwe Ricken beim Performance Tuning Workshop begleiten dürfen und auch er hat die Performance Counter noch mal klar als Musthave im Werkzeugkoffer eines jeden DBA benannt. Was mir bis dahin nicht bewusst war: SQL Server legt individuelle Counter wie <strong>Compile/sec</strong>, <strong>AdHoc Queries/sec</strong> usw. an.</p>



<figure class="wp-block-image aligncenter size-full is-resized is-style-default" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--60)"><img loading="lazy" decoding="async" width="500" height="54" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing.png" alt="MINDBLOWING" class="wp-image-608" style="width:500px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing.png 500w, https://dbavonnebenan.de/wp-content/uploads/2025/07/mindblowing-300x32.png 300w" sizes="auto, (max-width: 500px) 100vw, 500px" /></figure>



<p>Die Möglichkeiten, Abhängigkeiten direkt zu visualisieren &#8211; endlos. Aber wie lange soll ich da sitzen, bis ich das im Windows Performance Monitor abgebildet habe, am besten Adhoc bei einem Kunden? </p>



<p>Uwe zeigte die Möglichkeiten im <strong>Windows Admin Center</strong> auf, welches Templates ermöglicht, die man flexibel auf Servern nutzen kann. Für System oder Umgebungen, in denen das Admin Center strategisch genutzt wird, definitiv eine sehr gut Wahl.</p>



<p>Aber zwei Probleme bleiben: Als Berater kann ich nicht bei jedem Kunden erst Software installieren und wenn das Admin Center doch läuft, scheitern meine englischen Templates an deutschen Systemen.</p>



<p style="margin-bottom:var(--wp--preset--spacing--50)">Nun war ich trotzdem so unglaublich angefixt von der Template-Idee und den Sql Server Möglichkeiten, dass nach Feierabend 4 Wochen Crunchtime im Kopf waren. Somit möchte ich euch an dieser Stelle mein erstes Modul in der PowerShell Gallery vorstellen</p>


https://www.powershellgallery.com/packages/psTerminalPerfCounter/0.1.0-preview


<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Die technischen Details, Entwicklungsstand findet ihr alle im Repository, ich möchte hier einfach die verschiedenen Möglichkeiten zeigen.</p>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Main Features</h2>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">100% PowerShell</h3>



<ul class="wp-block-list">
<li>keine externen Abhängigkeiten</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Terminal-Grafiken</h3>



<ul class="wp-block-list">
<li>natürlich ist es keine richtige Engine, je mehr Counter desto Hakeliger wird es, aber es reicht</li>



<li>Als Basis dient die Lösung von <a href="https://github.com/PrateekKumarSingh/Graphical" target="_blank" rel="noopener" title="">Prateek Singh</a>. Ohne diesen Fund hätte ich wohl dieses Problem erst lösen müssen, wofür ich vermutlich keine Zeit gehabt hätte. DANKE!!!</li>
</ul>



<figure class="wp-block-image aligncenter size-full is-resized" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40)"><img loading="lazy" decoding="async" width="632" height="757" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu.png" alt="" class="wp-image-609" style="width:383px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu.png 632w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_example_cpu-250x300.png 250w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<p></p>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Configuration Templates</h3>



<ul class="wp-block-list">
<li>jedes Element der Ausgabe wird über Configuration Files gesteuert</li>



<li>flexibel, transportabel</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Individualisierung der Datenausgaben </h3>



<ul class="wp-block-list">
<li>für jeden Counter im Configuration-File kann eine eigene Ausgabe genutzt werden
<ul class="wp-block-list">
<li>Table, Graph oder Beide</li>
</ul>
</li>



<li>beliebige Skalierungen der Achsen in Steps und Max</li>



<li>beliebige Einfärbung der Values auf Basis einer benutzerdefinierten ColorMap per Counter</li>



<li>Konvertierung der Values per Configuration-File ( Byte -&gt; KB -&gt; MB -&gt; GB )</li>
</ul>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Sprachunabhängig</h3>



<ul class="wp-block-list">
<li>Configuration Files basieren nicht auf den Counter-Pfaden sondern IDs</li>



<li>immer noch transportabel, egal welche Systemsprache</li>



<li>Translation von ID zu Counterpfad erfolgt erst zur Laufzeit</li>
</ul>



<ul class="wp-block-list">
<li>keine externen Abhängigkeiten</li>



<li>Ich wusste gar nicht, dass dies möglich ist mit Windows Bordmitteln. Auch hier bin ich durch Zufall über einen Blog gestolpert bei <a href="https://powershell.one/tricks/performance/performance-counters" target="_blank" rel="noopener" title="">PowerShell.ONE</a>. Auch hier ein dickes DANKE!!! Meine eigentliche Lösung sah mehrere Sprachvarianten ( DE, EN, FR, ES) vor, welche dann per auslesen der Systemlokalen gesetzt wurden.</li>
</ul>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Demo mit CPU Configuration</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Install module with required dependency (GripDevJsonSchemaValidator)
Install-Module -Name psTerminalPerfCounter -AllowPrerelease

# Import the module
Import-Module psTerminalPerfCounter

# Start Default Cpu Configuration
Start-tpcMonitor -ConfigName CPU
</pre></div>


<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-51c0b34d wp-block-columns-is-layout-flex" style="padding-top:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="552" height="110" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load.png" alt="" class="wp-image-610" style="width:622px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load.png 552w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_load-300x60.png 300w" sizes="auto, (max-width: 552px) 100vw, 552px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>Beim Laden der Konfiguration werden die Counter IDs zu Counterpfaden übersetzt und getestet</p>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-0884d4d2 wp-block-columns-is-layout-flex" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="613" height="155" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample.png" alt="" class="wp-image-611" style="width:629px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample.png 613w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_sample-300x76.png 300w" sizes="auto, (max-width: 613px) 100vw, 613px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>Wenn das erfolgreich war, werden die ersten Samples gesammelt, damit ein Graph angezeigt werden kann</p>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-e242d015 wp-block-columns-is-layout-flex" style="padding-top:var(--wp--preset--spacing--50);padding-right:0;padding-bottom:var(--wp--preset--spacing--50);padding-left:0">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="767" height="774" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run.png" alt="" class="wp-image-612" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run.png 767w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run-297x300.png 297w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfcounter_run-150x150.png 150w" sizes="auto, (max-width: 767px) 100vw, 767px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<p>Thats IT! </p>



<p>Innerhalb von 10 Sekunden 2 Performance Counter am Laufen, ohne Installation, ohne Sprachprobleme.</p>
</div>
</div>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Demo Counter Translation</h2>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Translate ID -&gt; Path
Get-tpcPerformanceCounterInfo 238-6
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="711" height="148" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id.png" alt="" class="wp-image-613" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id.png 711w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_id-300x62.png 300w" sizes="auto, (max-width: 711px) 100vw, 711px" /></figure>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Search Counter -&gt; Including ID
Get-tpcPerformanceCounterInfo &quot;Queue&quot;
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0"><img loading="lazy" decoding="async" width="1013" height="600" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1.png" alt="" class="wp-image-614" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1.png 1013w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1-300x178.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search1-768x455.png 768w" sizes="auto, (max-width: 1013px) 100vw, 1013px" /></figure>



<p class="has-contrast-color has-text-color has-link-color wp-elements-a85209788f442c1a4bfcffc9ae003cc8" style="margin-bottom:var(--wp--preset--spacing--50)"><em>Die Ausgabe ist stark beschnitten, da das ResultSet enorm ist</em></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Search Counter -&gt; Including ID
Get-tpcPerformanceCounterInfo &quot;Processor Queue&quot;
</pre></div>


<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="675" height="79" src="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2.png" alt="" class="wp-image-615" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2.png 675w, https://dbavonnebenan.de/wp-content/uploads/2025/07/perfmon_translate_search2-300x35.png 300w" sizes="auto, (max-width: 675px) 100vw, 675px" /></figure>



<div style="height:30px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">Wie geht&#8217;s weiter?</h2>



<p>Die Basis läuft, die planned Feature <a href="https://github.com/gabrielkoehl/psTerminalPerfCounter/blob/main/docs/en-US/DevelopmentStatus.MD" target="_blank" rel="noopener" title="">Liste</a> ist lang und das, was ich wirklich brauche bezüglich Sql Server ist noch in weiter Ferne. Ich hoffe ich habe nicht ein Problem bedient, was es gar nicht gibt, aber dieses Modul adressiert einfach alle Probleme, die ich schon immer mit den Performance Countern hatte, siehe oben.</p>



<p>Weiterhin ist das mein erstes größeres Projekt, was ich Public entwickle, somit könnte Github nicht immer den erwarteten Standards entsprechen. Über Feedback und Hinweise freue ich mich immer.</p>



<figure class="wp-block-image aligncenter size-full is-resized" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><img loading="lazy" decoding="async" width="246" height="470" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png" alt="happy" class="wp-image-330" style="width:125px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1.png 246w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_standing_1-157x300.png 157w" sizes="auto, (max-width: 246px) 100vw, 246px" /></figure>


https://github.com/gabrielkoehl/psTerminalPerfCounter<p>The post <a href="https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_de/">psTerminalPerfCounter – SpeedUp your Performance Counters in Terminal</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/psterminalperfcounter-speedup-your-performance-counters_de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PowerShell Classes for Beginners &#8211; Structured Code Instead of Function Chaos</title>
		<link>https://dbavonnebenan.de/powershell-classes-for-beginner-en/</link>
					<comments>https://dbavonnebenan.de/powershell-classes-for-beginner-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Sat, 14 Jun 2025 18:36:58 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Classes]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[oop]]></category>
		<category><![CDATA[TUTORIAL]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=528</guid>

					<description><![CDATA[<p>Intro I&#8217;ve always used PowerShell following the same pattern, that&#8217;s roughly how my learning path followed the PowerShell Best Practices. I&#8217;ve been operating on these last two levels for quite a long time now, of course getting more efficient and generally my PowerShell knowledge became broader. During this development, however, I noticed a problem: I&#8217;m [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/powershell-classes-for-beginner-en/">PowerShell Classes for Beginners – Structured Code Instead of Function Chaos</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading" style="margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>I&#8217;ve always used PowerShell following the same pattern, that&#8217;s roughly how my learning path followed the PowerShell Best Practices.</p>



<ul class="wp-block-list">
<li>Quick and Dirty one-liners in the console, nowadays Terminal</li>



<li>Gradually, those one-liners evolved into PowerShell scripts with better organized one-liners</li>



<li>Variables came along, then functions, reusability increased</li>



<li>Functions became modules, persistent data storage in JSON and technologically that was the end for a while</li>



<li>The last highlight was threading and runspaces</li>
</ul>



<p>I&#8217;ve been operating on these last two levels for quite a long time now, of course getting more efficient and generally my PowerShell knowledge became broader.</p>



<p>During this development, however, I noticed a problem: I&#8217;m a very organized person, at least in my professional environment 😉, and so should my code be, structured. I don&#8217;t think much of Clean Code, comments are just important at certain points, for me, for others, for my customers. Besides comments, structure is also important &#8211; what good do comments do me in a &#8218;bucket of chars&#8216;? I also love symmetrical code, yeah go ahead and laugh, but it immediately makes clear what&#8217;s a variable, what&#8217;s an assignment and what&#8217;s the value. When I see something like this&#8230;</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
$userInfo = @{
   Name = $varSurename
   Address = $varStreet
   Data = $varRandomLongVar
}

$configSettings = @{
   ServerName = &#039;localhost&#039;
   Port = 5432
   Database = &#039;mydb&#039;
   Timeout = 30
}

$employee = @{
   Personal = @{
       FirstName = $varSurename
       LastName = &#039;Doe&#039;
       Street = $varStreet
   }
   Work = @{
       Department = &#039;IT&#039;
       Position = &#039;Developer&#039;
   }
}
</pre></div></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="" class="wp-image-516" style="width:229px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="auto, (max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)">… my insides turn outward 🤣 Maybe it&#8217;s also a disease. This is a normal writing style that&#8217;s considered clean, I know, but it should look like this …</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
$userInfo = @{
   Name 	= $varSurename
   Address 	= $varStreet
   Data 	= $varRandomLongVar
}

$configSettings = @{
   ServerName 	= &#039;localhost&#039;
   Port 		= 5432
   Database 	= &#039;mydb&#039;
   Timeout 		= 30
}

$employee = @{
   Personal = @{
       FirstName 	= $varSurename
       LastName 	= &#039;Doe&#039;
       Street 		= $varStreet
   }
   Work 	= @{
       Department 	= &#039;IT&#039;
       Position 	= &#039;Developer&#039;
   }
}

$users = @(
   @{ Name = &#039;Alice&#039;; 		Role = &#039;Admin&#039; }
   @{ Name = $varSurename; 	Role = &#039;User&#039; }
)
</pre></div></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="251" height="330" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5.png" alt="" class="wp-image-515" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5.png 251w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5-228x300.png 228w" sizes="auto, (max-width: 251px) 100vw, 251px" /></figure>
</div>
</div>



<p style="margin-top:var(--wp--preset--spacing--50);margin-bottom:0">Anyway, that&#8217;s not what this is about. It&#8217;s basically the case that structure and order gradually decrease in larger projects and classes play exactly into this topic. But why only now? Classes have existed since PowerShell 5.0.</p>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">AI Assisted Coding</h2>



<p>Of course I was an early adopter of all the AI chatbots and was amazed at what ChatGPT could already do 2 years ago. Of course the code, regardless of language, was unusable, but definitely useful for knowledge generation. As the quality progressed, the code got better and the solution approaches became more and more interesting.</p>



<p>At some point, classes started appearing in the suggestions and I consistently ignored them 😝. Through my C# journeys, I knew that classes are simply the standard, but you should fundamentally know what you want to do with them in the project to design them cleanly. And I didn&#8217;t want to make that effort.</p>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">The New Project</h2>



<p>It was time to adapt a 4-year-old solution to new requirements, as is sometimes the case. Since my knowledge doesn&#8217;t stand still either, after briefly skimming through the code, I decided:</p>



<p class="has-text-align-center" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>I&#8217;M NOT TOUCHING THIS ANYMORE</strong>, <strong>THIS ALL HAS TO BE NEW!!</strong></p>



<p>Now I was in the relaxed position that everything was available for the solution:</p>



<ul class="wp-block-list">
<li>Program flow chart</li>



<li>Function descriptions</li>



<li>Programmatic solution of the required functions at the lowest level</li>



<li>4 years of lessons learned</li>
</ul>



<p>So I only had to worry about orchestrating all the steps and decided to give classes a chance. In theory, this is namely the cleanest and most structured way to code.</p>



<p>The result hasn&#8217;t completely changed my way of coding, but it has sustainably changed it &#8211; but more on that later.</p>



<h2 class="wp-block-heading" id="powershell-classes-conclusion" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Conclusion After 5 Weeks in the Project</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="532" height="500" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection.jpg" alt="I SAID PERFECTION" class="has-border-color has-contrast-border-color wp-image-518" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection.jpg 532w, https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection-300x282.jpg 300w" sizes="auto, (max-width: 532px) 100vw, 532px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<p>Classes in PowerShell are so incredibly simple, so structured, so beautiful❤️. Of course the use case has to fit, since the implementation initially requires more structure, but when that&#8217;s given, there will be no way around classes for me anymore.</p>
</div>
</div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Why This Article, Aren&#8217;t There Enough Already?</h2>



<p>There are some articles about classes in PowerShell and object orientation, but still this topic is hardly covered in a classic tutorial. It&#8217;s always the functions and you have to know about this topic to find it. So I&#8217;m simply writing another article, also in german this time, to maybe reach one or the other additionally, because&#8230;</p>



<figure class="wp-block-image aligncenter size-large is-resized" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"><img loading="lazy" decoding="async" width="1024" height="191" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-1024x191.png" alt="" class="wp-image-517" style="width:493px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-1024x191.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-300x56.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-768x143.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw.png 1153w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--50)">Background Info on PowerShell Classes &#8211; OOP Since Version 5.0</h2>



<p>Classes have existed in PowerShell since PowerShell 5.0, so 2016&#8230; and I only learned about them a year ago 🤦‍♂️. PowerShell classes bring object-oriented programming concepts to PowerShell and thus also have all the basic features of a class.</p>



<ul class="wp-block-list">
<li><strong>Properties</strong>: Properties are strongly typed and support most validation attributes like [ValidateNotNull()], [ValidateRange()], etc.</li>



<li><strong>Methods</strong>: Methods are functions bound to an object and must explicitly define the return type</li>



<li><strong>Constructors</strong>: Enable initialization of objects with specific values</li>



<li><strong>Inheritance</strong>: Allows creating new classes from existing classes and extending their functionality</li>
</ul>



<p>A class, after loading, is its own data type &#8211; more on that later.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--50)">And How Does This Work Now?</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="Explanation incoming" class="wp-image-345" style="width:143px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>I still remember how I struggled with OOP many years ago and it&#8217;s generally assumed that rather admins than full-blood software developers initially deal with PowerShell. So I&#8217;ll try to keep it totally simple.</p>
</div>
</div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>I&#8217;ll take a fictional module that queries server properties via remoting and evaluates them locally, a classic admin task.</p>



<p><strong>ATTENTION:</strong> Of course this example is very constructed and not necessarily practical, but in my opinion it shows the relationships quite well.</p>



<p>This PowerShell classes tutorial shows you the most important concepts for object-oriented programming with PowerShell.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Properties</h3>



<p style="margin-top:0">A class has a firmly defined set of properties at all times. This cannot be changed at runtime, but must be described in the definition of the class.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">The Class</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Constructors</h3>



<p style="margin-bottom:var(--wp--preset--spacing--50)">A constructor creates the instance of a class. The class can receive default values, or the constructor expects parameters. In our case, a server name makes sense. So I give the property <strong>ServerName</strong> a value directly when loading.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------

    # Constructor
    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Now we have a class, but how do we get a server in there? By loading the class into a variable and preferably also dynamically for all servers.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------

    # Constructor
    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }
}

$serverlist = @(&#039;Server1&#039;,&#039;Server2&#039;,&#039;Server3&#039;)

foreach ( $server in $serverlist ) {
    
    # Create a new Server object for each server name
    New-Variable -Name &quot;obj_$server&quot; -Value (&#x5B;Server]::new($server)) -Force

}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Methods</h3>



<p>Methods make classes really interesting in my opinion. Methods are the functions with which I can manipulate properties of classes or generate RETURNS based on arguments that I add to the call. Here I already specify the type of return in the method definition. <strong>VOID</strong> thus logically creates no return.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem

    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }

    &#x5B;void] GetServerInfo() {

        try {

            $osInfo = Invoke-Command -ComputerName $this.ServerName -ScriptBlock {
                Get-CimInstance Win32_OperatingSystem | Select-Object Caption, LastBootUpTime
            } -ErrorAction Stop

            $this.IP = Invoke-Command -ComputerName $this.ServerName -ScriptBlock {
                (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -notlike &#039;169.*&#039;} | Select-Object -First 1).IPAddress
            } -ErrorAction Stop

            $this.OperatingSystem = $osInfo.Caption
            $this.LastBootTime    = $osInfo.LastBootUpTime
            $this.Status          = &quot;Online&quot;

        } catch {

            $this.Status = &quot;Offline&quot;

        }
    }
}

$serverlist = @(&#039;Server1&#039;,&#039;Server2&#039;,&#039;Server3&#039;)

foreach ($server in $serverlist) {
    New-Variable -Name &quot;obj_$server&quot; -Value (&#x5B;Server]::new($server)) -Force
}

$obj_Server1.GetServerInfo()
$obj_Server2.GetServerInfo()
$obj_Server3.GetServerInfo()
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Here I&#8217;ve inserted the method <strong>GetServerInfo()</strong>, which now collects the information about the server. I don&#8217;t need to pass an argument or start a cmdlet, since the instance (server name) of the class (Server) has inherited everything. I don&#8217;t need external functions, variables or anything else, everything is available in the class. ❤️ Following an example from the LAB:</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--30)"><img loading="lazy" decoding="async" width="542" height="283" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab.png" alt="LAB Result of simple powershell class" class="wp-image-532" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab.png 542w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab-300x157.png 300w" sizes="auto, (max-width: 542px) 100vw, 542px" /><figcaption class="wp-element-caption">LAB Results</figcaption></figure>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">And Now, Where&#8217;s the Benefit?</h2>



<p>Of course this isn&#8217;t the most optimal use case, but it&#8217;s understandable. One possibility would be to create a logging class. Here from my project:</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class PatchLogger {
     &#x5B;string]        $ServerName
     &#x5B;string]        $LogPathServer
     &#x5B;string]        $LogLevel = &#039;INFO&#039;
     &#x5B;hashtable]     $LogTargets
     &#x5B;bool]          $ConsoleOutput
     &#x5B;bool]          $StreamOutput
     &#x5B;string]        $VerbosePreference
     &#x5B;string]        $DebugPreference

     # Constructor
     PatchLogger(	&#x5B;string]$LogPathServer,
					&#x5B;string]$ServerName,
					&#x5B;string]$MessageFile,
					&#x5B;string]$ErrorFile,
					&#x5B;string]$VerboseFile,
					&#x5B;string]$DebugFile,
					&#x5B;string]$SqlServerFile,
					&#x5B;bool]$ConsoleOutput 	= $true,
					&#x5B;bool]$StreamOutput 	= $false) {
					
          $this.LogPathServer   = $LogPathServer
          $this.ServerName      = $ServerName
          $this.ConsoleOutput   = $ConsoleOutput
          $this.StreamOutput    = $StreamOutput
          $this.LogTargets      = @{
               Main           = $MessageFile
               Error          = $ErrorFile
               Verbose        = $VerboseFile
               Debug          = $DebugFile
               SqlServer      = $SqlServerFile
          }

          $this.VerbosePreference = $Global:VerbosePreference
          $this.DebugPreference   = $Global:DebugPreference

          if ( -not (Test-Path $this.LogPathServer) ) {
               New-Item -Path $this.LogPathServer -ItemType Directory -Force | Out-Null
          }
    }
	
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>An instance of the class is loaded directly into the variable <strong>$PatchLogger</strong> when the module loads, and from now on I can start right away with <strong>$PatchLogger.Info($message)</strong>. How? Such a short command with such a complex class? Let&#8217;s get to the last thing, which in my opinion is the advantage par excellence: <strong>Overloading</strong>.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Overloading</h2>



<p>Overloading means nothing other than the number of arguments I pass to a method. Based on the number, the engine can distinguish which method is used, since I can have several with the same name. The technical term would be Method Overloading.</p>



<p>The added value lies in the fact that I don&#8217;t have to work with BoundParams, Switch or IF loops like with functions. When a helper method is called with only one argument, the missing arguments are filled in internally and the main method is called.</p>



<p>Let&#8217;s take the example <strong>$PatchLogger.Info($message)</strong></p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Hauptmethode
&#x5B;hashtable] WriteLog(&#x5B;string]$Level, &#x5B;string]$Message, &#x5B;string]$MessageCode, &#x5B;int]$Severity, &#x5B;string]$Target, &#x5B;bool]$ReturnObject) {...}

# Helfermethode
&#x5B;hashtable] Info(&#x5B;string]$Message) {
      return $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $true)
}

# Helfermethode 2
&#x5B;hashtable] Info(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
      return $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $true)
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>What will be most needed in a large module? A simple message to the main log, without message code and frills. So the helper method calls the main method with the default values that do exactly that.</p>



<p>Let&#8217;s look at helper method 2. This expects 2 arguments, namely a MessageCode. Now I can write a registered message with <strong>$PatchLogger.Info(&#8222;Process XY completed&#8220;,&#8220;I0002&#8243;)</strong>.</p>



<p>Of course I can also implement all this with functions, but I don&#8217;t think it&#8217;s as structured and pretty.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Complete PatchLogger Class</summary><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class PatchLogger {
     &#x5B;string]        $ServerName
     &#x5B;string]        $LogPathServer
     &#x5B;string]        $LogLevel = &#039;INFO&#039;
     &#x5B;hashtable]     $LogTargets
     &#x5B;bool]          $ConsoleOutput
     &#x5B;bool]          $StreamOutput
     &#x5B;string]        $VerbosePreference
     &#x5B;string]        $DebugPreference

     # Constructor
     PatchLogger(&#x5B;string]$LogPathServer, &#x5B;string]$ServerName, &#x5B;string]$MessageFile, &#x5B;string]$ErrorFile, &#x5B;string]$VerboseFile, &#x5B;string]$DebugFile, &#x5B;string]$SqlServerFile, &#x5B;bool]$ConsoleOutput = $true, &#x5B;bool]$StreamOutput = $false) {
          $this.LogPathServer   = $LogPathServer
          $this.ServerName      = $ServerName
          $this.ConsoleOutput   = $ConsoleOutput
          $this.StreamOutput    = $StreamOutput
          $this.LogTargets      = @{
               Main           = $MessageFile
               Error          = $ErrorFile
               Verbose        = $VerboseFile
               Debug          = $DebugFile
               SqlServer      = $SqlServerFile
          }

          $this.VerbosePreference = $Global:VerbosePreference
          $this.DebugPreference   = $Global:DebugPreference

          if ( -not (Test-Path $this.LogPathServer) ) {
               New-Item -Path $this.LogPathServer -ItemType Directory -Force | Out-Null
          }
     }

     &#x5B;void] UpdatePreferences(&#x5B;string]$VerbosePreference, &#x5B;string]$DebugPreference) {
          $this.VerbosePreference = $VerbosePreference
          $this.DebugPreference   = $DebugPreference
     }

     # Central WriteLog method - all other methods call this one
     &#x5B;object] WriteLog(&#x5B;string]$Level, &#x5B;string]$Message, &#x5B;string]$MessageCode, &#x5B;int]$Severity, &#x5B;string]$Target, &#x5B;bool]$ReturnObject) {

          $timestamp          = Get-Date -Format &quot;yyyy-MM-dd HH:mm:ss&quot;
          $severityText       = if ($Severity -ne 9999) { &quot;S$Severity&quot; } else { &quot;&quot; }
          $messageCodeText    = if ($MessageCode) { &quot; $MessageCode&quot; } else { &quot;&quot; }

          $paddedLevel        = &quot;{0,-9}&quot; -f &quot;&#x5B;$Level]&quot;

          $additionalInfo     = &quot;$severityText$messageCodeText&quot;
          $logEntry           = if ( $level -eq &#039;BLANK&#039; ) {
                                   &quot;&quot;
                                } elseif ( $Level -notin @(&#039;VERBOSE&#039;,&#039;DEBUG&#039;) -and -not &#x5B;string]::IsNullOrEmpty($additionalInfo) ) {
                                   &quot;&#x5B;$timestamp] $paddedLevel &#x5B;$($this.ServerName)] $Message ($additionalInfo)&quot;
                                } else {
                                   &quot;&#x5B;$timestamp] $paddedLevel &#x5B;$($this.ServerName)] $Message&quot;
                              }

          $targetFile = $this.LogTargets&#x5B;$Target]
          if ( $targetFile ) {
               try {
                    Add-Content -Path $targetFile -Value $logEntry -Encoding UTF8

                    $verboseFile = $this.LogTargets&#x5B;&#039;Verbose&#039;]
                    if ( $verboseFile -and $Level -ne &#039;VERBOSE&#039; ) {
                         Add-Content -Path $verboseFile -Value $logEntry -Encoding UTF8 # for unbroken verbose stream
                    }

               } catch {
                    Invoke-ScriptError $_
               }
          }

          if ( $this.ConsoleOutput ) {
               switch ( $Level ) {
                    &#039;ERROR&#039;        { Write-Host $logEntry -ForegroundColor Red }
                    &#039;WARNING&#039;      { Write-Host $logEntry -ForegroundColor Yellow }
                    &#039;INFO&#039;         { Write-Host $logEntry -ForegroundColor Green }
                    &#039;BLANK&#039;        { Write-Host $logEntry}
                    &#039;VERBOSE&#039;      {
                         if ( $this.VerbosePreference -ne &#039;SilentlyContinue&#039; ) {
                              Write-Host $logEntry -ForegroundColor Cyan
                         }
                    }
                    &#039;DEBUG&#039;        {
                         if ( $this.DebugPreference -ne &#039;SilentlyContinue&#039; ) {
                              Write-Host $logEntry -ForegroundColor Magenta
                         }
                    }
               }
          }

          # PowerShell streams for integration - only if explicitly enabled
          if ( $this.StreamOutput ) {
               switch ($Level) {
                    &#039;ERROR&#039;   { Write-Error       $Message -ErrorAction Continue }
                    &#039;WARNING&#039; { Write-Warning     $Message }
                    &#039;VERBOSE&#039; { Write-Verbose     $Message }
                    &#039;DEBUG&#039;   { Write-Debug       $Message }
               }
          }

          # Return object only when requested
          if ( $ReturnObject ) {
               return @{
                    HasError    = $Level -eq &#039;ERROR&#039;
                    HasWarning  = $Level -eq &#039;WARNING&#039;
                    MessageCode = if ( $MessageCode ) { $MessageCode } else { $null }
                    Message     = $Message
                    Severity    = $Severity
                    Level       = $Level
                    Target      = $Target
               }
          }

          return $null
     }

     # BLANK Line
     &#x5B;void] BlankLine() {
          $this.WriteLog(&#039;BLANK&#039;, &#039;&#039;, $null, 0, &#039;Main&#039;, $false)
     }

     # INFO Methods - standard return hashtable
     &#x5B;hashtable] Info(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $true)
     }

     &#x5B;hashtable] Info(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $true)
     }

     # INFO NoReturn
     &#x5B;void] InfoNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $false)
     }

     &#x5B;void] InfoNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $false)
     }

     # WARNING Methods - standard return hashtable
     &#x5B;hashtable] Warning(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;WARNING&#039;, $Message, $null, 1, &#039;Main&#039;, $true)
     }

     &#x5B;hashtable] Warning(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;WARNING&#039;, $Message, $MessageCode, 1, &#039;Main&#039;, $true)
     }

     # WARNING NoReturn
     &#x5B;void] WarningNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;WARNING&#039;, $Message, $null, 1, &#039;Main&#039;, $false)
     }

     &#x5B;void] WarningNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;WARNING&#039;, $Message, $MessageCode, 1, &#039;Main&#039;, $false)
     }

     # ERROR Methods - standard return hashtable
     &#x5B;hashtable] Error(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;ERROR&#039;, $Message, $null, 2, &#039;Error&#039;, $true)
     }

     &#x5B;hashtable] Error(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;ERROR&#039;, $Message, $MessageCode, 2, &#039;Error&#039;, $true)
     }

     # ERROR NoReturn
     &#x5B;void] ErrorNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;ERROR&#039;, $Message, $null, 2, &#039;Error&#039;, $false)
     }

     &#x5B;void] ErrorNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;ERROR&#039;, $Message, $MessageCode, 2, &#039;Error&#039;, $false)
     }

     # VERBOSE &amp; DEBUG - NoReturn
     &#x5B;void] Verbose(&#x5B;string]$Message) {
          $this.WriteLog(&#039;VERBOSE&#039;, $Message, $null, 0, &#039;Verbose&#039;, $false)
     }

     &#x5B;void] Debug(&#x5B;string]$Message) {
          $this.WriteLog(&#039;DEBUG&#039;, $Message, $null, 0, &#039;Debug&#039;, $false)
     }

     # Step/SQL/AG Methods - NoReturn
     &#x5B;void] StepStart(&#x5B;int]$Step, &#x5B;string]$StepName) {
          $this.BlankLine()
          $message = &quot;========================= STEP $Step START: $StepName =========================&quot;
          $this.WriteLog(&#039;INFO&#039;, $message, $null, 9999, &#039;Main&#039;, $false)
     }

     &#x5B;void] StepEnd(&#x5B;int]$Step, &#x5B;string]$StepName, &#x5B;bool]$Success) {
          $status   = if ( $Success ) { &quot;SUCCESS&quot; } else { &quot;FAILED&quot; }
          $message  = &quot;========================= STEP $Step END: $StepName - $status =========================&quot;
          $this.WriteLog(&#039;INFO&#039;, $message, $null, 9999, &#039;Main&#039;, $false)
          $this.BlankLine()
     }

}
</pre></div></details>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Summary</h2>



<p>PowerShell classes revolutionize how you structure larger projects. Available since PowerShell 5.0, they offer object-oriented programming with properties, methods, constructors, and method overloading. The initial extra effort pays off through clean, maintainable code &#8211; especially for modules and more complex function collections.</p>



<p><strong>The most important advantages at a glance:</strong></p>



<ul class="wp-block-list">
<li>Strongly typed properties with validation</li>



<li>Encapsulated functionality through methods</li>



<li>Elegant overloading instead of complex parameter logic and a structure that remains understandable even after months</li>
</ul>



<p>For daily-doing scripts, the classic approach remains more practical, but as soon as reusability and structure become important, there&#8217;s no way around classes for me anymore. 🫡</p>



<div style="height:75px" aria-hidden="true" class="wp-block-spacer"></div>


<figure style="width:250px;height:250px;" class="wp-block-post-featured-image"><a href="https://dbavonnebenan.de/powershell-classes-for-beginner-en/" target="_self"  style="height:250px"><img loading="lazy" decoding="async" width="256" height="256" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="PowerShell Classes for Beginners &#8211; Structured Code Instead of Function Chaos" style="height:250px;object-fit:cover;" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes.png 256w, https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes-150x150.png 150w" sizes="auto, (max-width: 256px) 100vw, 256px" /></a></figure><p>The post <a href="https://dbavonnebenan.de/powershell-classes-for-beginner-en/">PowerShell Classes for Beginners – Structured Code Instead of Function Chaos</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/powershell-classes-for-beginner-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PowerShell Klassen für Anfänger &#8211; Strukturierter Code statt Funktions-Chaos</title>
		<link>https://dbavonnebenan.de/powershell-classes-for-beginner-de/</link>
					<comments>https://dbavonnebenan.de/powershell-classes-for-beginner-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Sat, 14 Jun 2025 18:33:32 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Classes]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[Klassen]]></category>
		<category><![CDATA[TUTORIAL]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=511</guid>

					<description><![CDATA[<p>Intro Seit eh und je nutze ich PowerShell nach Schema F, so ungefähr war auch mein Lernpfad entsprechend PowerShell Best Practices. Auf den letzten beiden Ebenen habe ich mich nun sehr lange bewegt, natürlich immer effizienter und allgemein wurde das PowerShell Wissen breiter. Während dieser Entwicklung merkte ich jedoch ein Problem: Ich bin ein sehr [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/powershell-classes-for-beginner-de/">PowerShell Klassen für Anfänger – Strukturierter Code statt Funktions-Chaos</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading" style="margin-top:0;margin-bottom:var(--wp--preset--spacing--50)">Intro</h2>



<p>Seit eh und je nutze ich PowerShell nach Schema F, so ungefähr war auch mein Lernpfad entsprechend PowerShell Best Practices.</p>



<ul class="wp-block-list">
<li>Quick and Dirty Einzeiler in der Konsole, mittlerweile Terminal</li>



<li>Nach und nach wurden aus den Einzeilern PowerShell Scripte mit besser organisierten Einzeilern</li>



<li>Es kamen Variablen, dann Funktionen, die Wiederverwendbarkeit stieg</li>



<li>Aus Funktionen wurden Module, persistente Datenhalden in Json und technologisch war das erstmal das Ende</li>



<li>Das letzte HighLight war Threading und Runspaces</li>
</ul>



<p>Auf den letzten beiden Ebenen habe ich mich nun sehr lange bewegt, natürlich immer effizienter und allgemein wurde das PowerShell Wissen breiter.</p>



<p>Während dieser Entwicklung merkte ich jedoch ein Problem: Ich bin ein sehr organisierter Mensch, zumindestens im beruflichen Umfeld 😉, und so soll auch mein Code sein, strukturiert. Von Clean Code halte ich nichts, Kommentare sind einfach punktuell wichtig, für mich, für andere, für meine Kunden. Neben Kommentaren ist aber auch die Struktur wichtig, was bringen mir Kommentare an einem &#8218;Bucket of Chars&#8216; ? Ebenfalls liebe ich symmetrischen Code, ja lacht ruhig, aber es macht sofort ersichtlich, was ist eine Variable, was eine Zusweisung und was der Value. Wenn ich sowas sehe&#8230;</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
$userInfo = @{
   Name = $varSurename
   Address = $varStreet
   Data = $varRandomLongVar
}

$configSettings = @{
   ServerName = &#039;localhost&#039;
   Port = 5432
   Database = &#039;mydb&#039;
   Timeout = 30
}

$employee = @{
   Personal = @{
       FirstName = $varSurename
       LastName = &#039;Doe&#039;
       Street = $varStreet
   }
   Work = @{
       Department = &#039;IT&#039;
       Position = &#039;Developer&#039;
   }
}
</pre></div></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="264" height="329" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png" alt="" class="wp-image-516" style="width:229px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4.png 264w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-4-241x300.png 241w" sizes="auto, (max-width: 264px) 100vw, 264px" /></figure>
</div>
</div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p style="margin-top:0;margin-bottom:0">&#8230; da kehrt sich mein Innerstes nach Außen 🤣 Ist vielleicht auch eine Krankheit. Dies ist eine normale Schreibweise, die als sauber gilt, ich weiß, aber das sollte doch so aussehen..</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%"><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
$userInfo = @{
   Name 	= $varSurename
   Address 	= $varStreet
   Data 	= $varRandomLongVar
}

$configSettings = @{
   ServerName 	= &#039;localhost&#039;
   Port 		= 5432
   Database 	= &#039;mydb&#039;
   Timeout 		= 30
}

$employee = @{
   Personal = @{
       FirstName 	= $varSurename
       LastName 	= &#039;Doe&#039;
       Street 		= $varStreet
   }
   Work 	= @{
       Department 	= &#039;IT&#039;
       Position 	= &#039;Developer&#039;
   }
}

$users = @(
   @{ Name = &#039;Alice&#039;; 		Role = &#039;Admin&#039; }
   @{ Name = $varSurename; 	Role = &#039;User&#039; }
)
</pre></div></div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="251" height="330" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5.png" alt="" class="wp-image-515" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5.png 251w, https://dbavonnebenan.de/wp-content/uploads/2025/06/emote-5-228x300.png 228w" sizes="auto, (max-width: 251px) 100vw, 251px" /></figure>
</div>
</div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p style="margin-top:0;margin-bottom:0">Wie auch immer, darum solls hier nicht gehen. Es ist grundsätzlich so, dass die Struktur und Ordnung bei größeren Projekten nach und nach abnimmt und Klassen genau auf dieses Thema einspielen. Aber warum jetzt erst? Klassen gibt es seit PowerShell 5.0 .</p>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--40)">AI Assisted Coding</h2>



<p>Natürlich war ich early Adopter der ganzen KI Chatbots und war erstaunt, was ChatGPT vor 2 Jahren schon konnte. Natürlich war der Code, egal welche Sprache, unbrauchbar, aber für Wissenserzeugung allemal nützlich. So wurde mit fortlaufender Qualität der Code besser und auch die Lösungswege wurden immer interessanter. </p>



<p>Irgendwann sind in den Vorschlägen Klassen aufgetaucht und ich habe es konsequent ignoriert 😝. Durch meine C# Reisen wusste ich, dass Klassen einfach der Standard sind, man aber schon grundlegend Wissen sollte, was ich damit machen will im Projekt, um diese sauber zu entwerfen. Und diesen Aufwand wollte ich mir nicht machen.</p>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--40)">Das neue Projekt</h2>



<p>Es war an der Zeit, eine 4 Jahre alte Lösung an neue Anforderungen anzupassen, wie das halt manchmal so der Fall ist. Da mein Wissen ja auch nicht stehen bleibt, habe ich nach kurzem überfliegen des Codes entschieden:</p>



<p class="has-text-align-center" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"><strong>DAS FASSE ICH NICHT MEHR AN</strong>, <strong>DAS MUSS ALLES NEU!!</strong></p>



<p>Nun war ich in der entspannten Lage, dass für die Lösung alles vorhanden war</p>



<ul class="wp-block-list">
<li>Programmablaufplan (PAP)</li>



<li>Funktionsbeschreibungen</li>



<li>Programmatische Lösung der benötigten Funktionen auf unterster Ebene</li>



<li>4 Jahre Lessons learned</li>
</ul>



<p>Somit musste ich mich nur um die Orchestrierung aller Schritte kümmern und habe entschieden, dass ich den Klassen mal eine Chance geben werde. In der Theorie ist dies nämlich die sauberste und strukturierteste Möglichkeit zu Coden.</p>



<p>Das Ergebnis hat meine Art zu Coden nicht völlig, aber nachhaltig verändert &#8211; aber dazu später mehr.</p>



<h2 class="wp-block-heading" id="powershell-classes-conclusion" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Fazit nach 5 Wochen im Projekt</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="532" height="500" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection.jpg" alt="I SAID PERFECTION" class="has-border-color has-contrast-border-color wp-image-518" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection.jpg 532w, https://dbavonnebenan.de/wp-content/uploads/2025/06/class-perfection-300x282.jpg 300w" sizes="auto, (max-width: 532px) 100vw, 532px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<p>Klassen in PowerShell sind so unglaublich simpel, so strukturiert, so wunderschön❤️. Natürlich muss der UseCase passen, da die Implementierung initial mehr Struktur verlangt, aber wenn dieser gegeben ist, wird für mich kein Weg mehr an Klassen vorbei führen. </p>
</div>
</div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--50)">Warum dieser Artikel, gibt doch genug?</h2>



<p>Es gibt einige Artikel über Klassen in PowerShell und Objektorientierung aber trotzdem wird dieses Thema kaum in einem klassischen Tutorial behandelt. Es sind immer die Funktionen und man muss um dieses Thema wissen um es zu finden. Somit schreibe ich einfach noch einen Artikel, auch mal in deutsch, um vielleicht doch den ein oder anderen zusätzlich zu erreichen, weil&#8230;</p>



<figure class="wp-block-image aligncenter size-large is-resized" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"><img loading="lazy" decoding="async" width="1024" height="191" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-1024x191.png" alt="" class="wp-image-517" style="width:493px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-1024x191.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-300x56.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw-768x143.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell-classes-ftw.png 1153w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--60);margin-right:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--60)">Hintergrundinfos zu PowerShell Klassen &#8211; OOP seit Version 5.0</h2>



<p style="margin-bottom:var(--wp--preset--spacing--30)">Klassen gibt es in PowerShell seit PowerShell 5.0 also 2016&#8230; und ich habe vor einem Jahr erst davon erfahren 🤦‍♂️. PowerShell Klassen bringen objektorientierte Programmierkonzepte in PowerShell und haben somit auch haben alle grundlegenden Features einer Klasse.</p>



<ul class="wp-block-list">
<li><strong>Eigenschaften (Properties)</strong>: Eigenschaften sind stark typisiert und unterstützen die meisten Validierungsattribute wie [ValidateNotNull()], [ValidateRange()] etc. </li>



<li><strong>Methoden</strong>: Methoden sind Funktionen, die an ein Objekt gebunden sind und explizit den Rückgabetyp definieren müssen</li>



<li><strong>Konstruktoren</strong>: Ermöglichen die Initialisierung von Objekten mit spezifischen Werten </li>



<li><strong>Vererbung</strong>: Erlaubt es, neue Klassen von bestehenden Klassen zu erstellen und deren Funktionalität zu erweitern</li>
</ul>



<p style="margin-top:var(--wp--preset--spacing--30)">Eine Klasse ist nach dem Laden ein eigener Datentyp, später mehr dazu.</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--70);margin-bottom:var(--wp--preset--spacing--70)"/>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--50)">Und wie funktioniert das jetzt?</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:20%">
<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="307" height="328" src="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png" alt="Explanation incoming" class="wp-image-345" style="width:143px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2.png 307w, https://dbavonnebenan.de/wp-content/uploads/2025/05/emotes_2-281x300.png 281w" sizes="auto, (max-width: 307px) 100vw, 307px" /></figure>
</div>



<div class="wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:80%">
<p>Ich weiß noch, wie ich mich vor vielen Jahren mit OOP schwer tat und allgemein davon auszugehen ist, dass sich doch eher Admins als vollblut Software Entwickler initial mit PowerShell auseinander setzen. Somit versuche ich es mal total simpel.</p>
</div>
</div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Ich nehme mal ein fiktives Modul, welche per Remoting Serverproperties abfragt und lokal auswertet, ein klassischer AdminTask. </p>



<p><strong>ACHTUNG:</strong> Natürlich ist dieses Beispiel sehr konstruiert und nicht zwingend praktikabel aber es zeigt meiner Meinung nach die Zusammenhänge recht gut.</p>



<p>Dieses PowerShell Klassen Tutorial zeigt dir die wichtigsten Konzepte für objektorientierte Programmierung mit PowerShell.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Eigenschaften (Properties)</h3>



<p style="margin-top:0">Eine Klasse hat zu jeder Zeit ein fest definierten Satz an Eigenschaften. Dieser kann zur Laufzeit nicht geändert werden, sondern muss in der Definition der Klasse beschrieben werden.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Die Klasse</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Konstruktoren</h3>



<p style="margin-bottom:var(--wp--preset--spacing--50)">Ein Konstruktor erzeugt die Instanz einer Klasse. Dabei kann die Klasse default Values erhalten, oder der Konstruktor erwartet Parameter. In unserem Fall ist ein Servername sinnvoll. Somit gebe ich der Property <strong>ServerName </strong>direkt beim Laden einen Wert.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------

    # Constructor
    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Nun haben wir zwar ein Klasse aber wie bekommen wir nun einen Server da rein? Indem wir die Klasse in eine Variable laden und am besten auch noch dynamisch für alle Server.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
# All Possible properties for the Server class
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem
# -------------------------------------------

    # Constructor
    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }
}

$serverlist = @(&#039;Server1&#039;,&#039;Server2&#039;,&#039;Server3&#039;)

foreach ( $server in $serverlist ) {
    
    # Create a new Server object for each server name
    New-Variable -Name &quot;obj_$server&quot; -Value (&#x5B;Server]::new($server)) -Force

}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--40);margin-right:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--40);margin-left:var(--wp--preset--spacing--20)">Methoden</h3>



<p>Methoden machen meiner Meinung nach die Klassen erst richtig interessant. Methoden sind die Funktionen mit denen ich Eigenschaften der Klassen manipulieren kann oder auf Basis von Argumenten, welche ich dem Aufruf hinzufüge, RETURNS erzeugen kann. Hierbei gebe ich bereits in der Methodendefintion den Typ der Rückgabe an. <strong>VOID</strong> erzeugt somit logischerweise keine Rückgabe.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class Server {
    &#x5B;string]        $ServerName
    &#x5B;string]        $Status
    &#x5B;ipaddress]     $IP 
    &#x5B;string]        $Location
    &#x5B;datetime]      $LastBootTime
    &#x5B;string]        $OperatingSystem

    Server(&#x5B;string]$name) {
        $this.ServerName = $name
    }

    &#x5B;void] GetServerInfo() {

        try {

            $osInfo = Invoke-Command -ComputerName $this.ServerName -ScriptBlock {
                Get-CimInstance Win32_OperatingSystem | Select-Object Caption, LastBootUpTime
            } -ErrorAction Stop

            $this.IP = Invoke-Command -ComputerName $this.ServerName -ScriptBlock {
                (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -notlike &#039;169.*&#039;} | Select-Object -First 1).IPAddress
            } -ErrorAction Stop

            $this.OperatingSystem = $osInfo.Caption
            $this.LastBootTime    = $osInfo.LastBootUpTime
            $this.Status          = &quot;Online&quot;

        } catch {

            $this.Status = &quot;Offline&quot;

        }
    }
}

$serverlist = @(&#039;Server1&#039;,&#039;Server2&#039;,&#039;Server3&#039;)

foreach ($server in $serverlist) {
    New-Variable -Name &quot;obj_$server&quot; -Value (&#x5B;Server]::new($server)) -Force
}

$obj_Server1.GetServerInfo()
$obj_Server2.GetServerInfo()
$obj_Server3.GetServerInfo()
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Hier habe ich die Methode <strong>GetServerInfo()</strong> eingefügt, die nun die Informationen zum Server sammelt. Dabei muss ich weder ein Argument mitgeben oder ein Cmdlet starten, da die Instanz (Servername) der Klasse (Server) alles vererbt bekommen hat. Ich brauche keine externen Funktionen, Variablen oder sonst was, alles in der Klasse vorhanden. ❤️ Folgend ein Beispiel aus dem LAB</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full" style="margin-top:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--30)"><img loading="lazy" decoding="async" width="542" height="283" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab.png" alt="LAB Result of simple powershell class" class="wp-image-532" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab.png 542w, https://dbavonnebenan.de/wp-content/uploads/2025/06/powershell_classes_lab-300x157.png 300w" sizes="auto, (max-width: 542px) 100vw, 542px" /><figcaption class="wp-element-caption">LAB Results</figcaption></figure>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--70);margin-right:0;margin-bottom:var(--wp--preset--spacing--50);margin-left:0">Und jetzt, wo ist der Nutzen?</h2>



<p>Natürlich ist das nicht der optimalste Use Case, dafür verständlich. Eine Möglichkeit wäre, eine LoggingKlasse zu erstellen. Anbei aus meinem Projekt:</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class PatchLogger {
     &#x5B;string]        $ServerName
     &#x5B;string]        $LogPathServer
     &#x5B;string]        $LogLevel = &#039;INFO&#039;
     &#x5B;hashtable]     $LogTargets
     &#x5B;bool]          $ConsoleOutput
     &#x5B;bool]          $StreamOutput
     &#x5B;string]        $VerbosePreference
     &#x5B;string]        $DebugPreference

     # Constructor
     PatchLogger(	&#x5B;string]$LogPathServer,
					&#x5B;string]$ServerName,
					&#x5B;string]$MessageFile,
					&#x5B;string]$ErrorFile,
					&#x5B;string]$VerboseFile,
					&#x5B;string]$DebugFile,
					&#x5B;string]$SqlServerFile,
					&#x5B;bool]$ConsoleOutput 	= $true,
					&#x5B;bool]$StreamOutput 	= $false) {
					
          $this.LogPathServer   = $LogPathServer
          $this.ServerName      = $ServerName
          $this.ConsoleOutput   = $ConsoleOutput
          $this.StreamOutput    = $StreamOutput
          $this.LogTargets      = @{
               Main           = $MessageFile
               Error          = $ErrorFile
               Verbose        = $VerboseFile
               Debug          = $DebugFile
               SqlServer      = $SqlServerFile
          }

          $this.VerbosePreference = $Global:VerbosePreference
          $this.DebugPreference   = $Global:DebugPreference

          if ( -not (Test-Path $this.LogPathServer) ) {
               New-Item -Path $this.LogPathServer -ItemType Directory -Force | Out-Null
          }
    }
	
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Eine Instanz der Klasse wird direkt beim Laden des Moduls in die Variable <strong>$PatchLogger </strong>geladen und ab jetzt kann ich mit <strong>$PatchLogger.Info($message)</strong> schon direkt loslegen. Wie? So ein kurzer Befehl bei einer so komplexen Klasse? Kommen wir zum letzten, was meiner Meinung nach der Vorteil schlechthin ist: <strong>Überladung</strong>.</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--50)">Überladung</h2>



<p>Überladung heisst nichts anderes als die Anzahl der Argumente, die ich einer Methode übergebe. Anhand der Anzahl kann die Engine unterscheiden, welche Methode genutzt wird, da ich mehrer mit den gleichen Namen haben kann. Der Fachbegriff wäre Method Overloading.</p>



<p>Der Mehrwert liegt darin, dass ich nicht wie bei Funktionen mit BoundParams, Switch oder IF SChleifen arbeiten muss. Wenn eine Helfer Methode mit nur einem Argument aufgerufen wird, werden intern die fehlenden Argumente aufgefüllt und die Hauptmethode aufgerufen.</p>



<p>Nehmen wir das Beispiel <strong>$PatchLogger.Info($message)</strong> </p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
# Hauptmethode
&#x5B;hashtable] WriteLog(&#x5B;string]$Level, &#x5B;string]$Message, &#x5B;string]$MessageCode, &#x5B;int]$Severity, &#x5B;string]$Target, &#x5B;bool]$ReturnObject) {...}

# Helfermethode
&#x5B;hashtable] Info(&#x5B;string]$Message) {
      return $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $true)
}

# Helfermethode 2
&#x5B;hashtable] Info(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
      return $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $true)
}
</pre></div>


<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Was wird das meiste sein, was ich ich einem großen Modul benötige? Eine einfache Message ins Main Log, ohne Messagecode und SchnickSchnack. Somit ruft die Helfermethode die Hauptmethode mit den Defaultwerten auf, die genau das bewirken.</p>



<p>Schauen wir uns Helfermethode 2 an. Diese erwartet 2 Argumente, nämlich einen MessageCode. Schon kann ich mit <strong>$PatchLogger.Info(&#8222;Prozess XY abgeschlossen&#8220;,&#8220;I0002&#8243;)</strong> eine registrierte Message schreiben. </p>



<p>Natürlich kann ich das auch alles mit Funktionen realisieren aber ich denke nicht so strukturiert und hübsch. </p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Gesamte PatchLogger Klasse</summary><div class="wp-block-syntaxhighlighter-code "><pre class="brush: powershell; title: ; notranslate">
class PatchLogger {
     &#x5B;string]        $ServerName
     &#x5B;string]        $LogPathServer
     &#x5B;string]        $LogLevel = &#039;INFO&#039;
     &#x5B;hashtable]     $LogTargets
     &#x5B;bool]          $ConsoleOutput
     &#x5B;bool]          $StreamOutput
     &#x5B;string]        $VerbosePreference
     &#x5B;string]        $DebugPreference

     # Constructor
     PatchLogger(&#x5B;string]$LogPathServer, &#x5B;string]$ServerName, &#x5B;string]$MessageFile, &#x5B;string]$ErrorFile, &#x5B;string]$VerboseFile, &#x5B;string]$DebugFile, &#x5B;string]$SqlServerFile, &#x5B;bool]$ConsoleOutput = $true, &#x5B;bool]$StreamOutput = $false) {
          $this.LogPathServer   = $LogPathServer
          $this.ServerName      = $ServerName
          $this.ConsoleOutput   = $ConsoleOutput
          $this.StreamOutput    = $StreamOutput
          $this.LogTargets      = @{
               Main           = $MessageFile
               Error          = $ErrorFile
               Verbose        = $VerboseFile
               Debug          = $DebugFile
               SqlServer      = $SqlServerFile
          }

          $this.VerbosePreference = $Global:VerbosePreference
          $this.DebugPreference   = $Global:DebugPreference

          if ( -not (Test-Path $this.LogPathServer) ) {
               New-Item -Path $this.LogPathServer -ItemType Directory -Force | Out-Null
          }
     }

     &#x5B;void] UpdatePreferences(&#x5B;string]$VerbosePreference, &#x5B;string]$DebugPreference) {
          $this.VerbosePreference = $VerbosePreference
          $this.DebugPreference   = $DebugPreference
     }

     # Central WriteLog method - all other methods call this one
     &#x5B;object] WriteLog(&#x5B;string]$Level, &#x5B;string]$Message, &#x5B;string]$MessageCode, &#x5B;int]$Severity, &#x5B;string]$Target, &#x5B;bool]$ReturnObject) {

          $timestamp          = Get-Date -Format &quot;yyyy-MM-dd HH:mm:ss&quot;
          $severityText       = if ($Severity -ne 9999) { &quot;S$Severity&quot; } else { &quot;&quot; }
          $messageCodeText    = if ($MessageCode) { &quot; $MessageCode&quot; } else { &quot;&quot; }

          $paddedLevel        = &quot;{0,-9}&quot; -f &quot;&#x5B;$Level]&quot;

          $additionalInfo     = &quot;$severityText$messageCodeText&quot;
          $logEntry           = if ( $level -eq &#039;BLANK&#039; ) {
                                   &quot;&quot;
                                } elseif ( $Level -notin @(&#039;VERBOSE&#039;,&#039;DEBUG&#039;) -and -not &#x5B;string]::IsNullOrEmpty($additionalInfo) ) {
                                   &quot;&#x5B;$timestamp] $paddedLevel &#x5B;$($this.ServerName)] $Message ($additionalInfo)&quot;
                                } else {
                                   &quot;&#x5B;$timestamp] $paddedLevel &#x5B;$($this.ServerName)] $Message&quot;
                              }

          $targetFile = $this.LogTargets&#x5B;$Target]
          if ( $targetFile ) {
               try {
                    Add-Content -Path $targetFile -Value $logEntry -Encoding UTF8

                    $verboseFile = $this.LogTargets&#x5B;&#039;Verbose&#039;]
                    if ( $verboseFile -and $Level -ne &#039;VERBOSE&#039; ) {
                         Add-Content -Path $verboseFile -Value $logEntry -Encoding UTF8 # for unbroken verbose stream
                    }

               } catch {
                    Invoke-ScriptError $_
               }
          }

          if ( $this.ConsoleOutput ) {
               switch ( $Level ) {
                    &#039;ERROR&#039;        { Write-Host $logEntry -ForegroundColor Red }
                    &#039;WARNING&#039;      { Write-Host $logEntry -ForegroundColor Yellow }
                    &#039;INFO&#039;         { Write-Host $logEntry -ForegroundColor Green }
                    &#039;BLANK&#039;        { Write-Host $logEntry}
                    &#039;VERBOSE&#039;      {
                         if ( $this.VerbosePreference -ne &#039;SilentlyContinue&#039; ) {
                              Write-Host $logEntry -ForegroundColor Cyan
                         }
                    }
                    &#039;DEBUG&#039;        {
                         if ( $this.DebugPreference -ne &#039;SilentlyContinue&#039; ) {
                              Write-Host $logEntry -ForegroundColor Magenta
                         }
                    }
               }
          }

          # PowerShell streams for integration - only if explicitly enabled
          if ( $this.StreamOutput ) {
               switch ($Level) {
                    &#039;ERROR&#039;   { Write-Error       $Message -ErrorAction Continue }
                    &#039;WARNING&#039; { Write-Warning     $Message }
                    &#039;VERBOSE&#039; { Write-Verbose     $Message }
                    &#039;DEBUG&#039;   { Write-Debug       $Message }
               }
          }

          # Return object only when requested
          if ( $ReturnObject ) {
               return @{
                    HasError    = $Level -eq &#039;ERROR&#039;
                    HasWarning  = $Level -eq &#039;WARNING&#039;
                    MessageCode = if ( $MessageCode ) { $MessageCode } else { $null }
                    Message     = $Message
                    Severity    = $Severity
                    Level       = $Level
                    Target      = $Target
               }
          }

          return $null
     }

     # BLANK Line
     &#x5B;void] BlankLine() {
          $this.WriteLog(&#039;BLANK&#039;, &#039;&#039;, $null, 0, &#039;Main&#039;, $false)
     }

     # INFO Methods - standard return hashtable
     &#x5B;hashtable] Info(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $true)
     }

     &#x5B;hashtable] Info(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $true)
     }

     # INFO NoReturn
     &#x5B;void] InfoNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;INFO&#039;, $Message, $null, 0, &#039;Main&#039;, $false)
     }

     &#x5B;void] InfoNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;INFO&#039;, $Message, $MessageCode, 0, &#039;Main&#039;, $false)
     }

     # WARNING Methods - standard return hashtable
     &#x5B;hashtable] Warning(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;WARNING&#039;, $Message, $null, 1, &#039;Main&#039;, $true)
     }

     &#x5B;hashtable] Warning(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;WARNING&#039;, $Message, $MessageCode, 1, &#039;Main&#039;, $true)
     }

     # WARNING NoReturn
     &#x5B;void] WarningNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;WARNING&#039;, $Message, $null, 1, &#039;Main&#039;, $false)
     }

     &#x5B;void] WarningNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;WARNING&#039;, $Message, $MessageCode, 1, &#039;Main&#039;, $false)
     }

     # ERROR Methods - standard return hashtable
     &#x5B;hashtable] Error(&#x5B;string]$Message) {
          return $this.WriteLog(&#039;ERROR&#039;, $Message, $null, 2, &#039;Error&#039;, $true)
     }

     &#x5B;hashtable] Error(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          return $this.WriteLog(&#039;ERROR&#039;, $Message, $MessageCode, 2, &#039;Error&#039;, $true)
     }

     # ERROR NoReturn
     &#x5B;void] ErrorNoReturn(&#x5B;string]$Message) {
          $this.WriteLog(&#039;ERROR&#039;, $Message, $null, 2, &#039;Error&#039;, $false)
     }

     &#x5B;void] ErrorNoReturn(&#x5B;string]$Message, &#x5B;string]$MessageCode) {
          $this.WriteLog(&#039;ERROR&#039;, $Message, $MessageCode, 2, &#039;Error&#039;, $false)
     }

     # VERBOSE &amp; DEBUG - NoReturn
     &#x5B;void] Verbose(&#x5B;string]$Message) {
          $this.WriteLog(&#039;VERBOSE&#039;, $Message, $null, 0, &#039;Verbose&#039;, $false)
     }

     &#x5B;void] Debug(&#x5B;string]$Message) {
          $this.WriteLog(&#039;DEBUG&#039;, $Message, $null, 0, &#039;Debug&#039;, $false)
     }

     # Step/SQL/AG Methods - NoReturn
     &#x5B;void] StepStart(&#x5B;int]$Step, &#x5B;string]$StepName) {
          $this.BlankLine()
          $message = &quot;========================= STEP $Step START: $StepName =========================&quot;
          $this.WriteLog(&#039;INFO&#039;, $message, $null, 9999, &#039;Main&#039;, $false)
     }

     &#x5B;void] StepEnd(&#x5B;int]$Step, &#x5B;string]$StepName, &#x5B;bool]$Success) {
          $status   = if ( $Success ) { &quot;SUCCESS&quot; } else { &quot;FAILED&quot; }
          $message  = &quot;========================= STEP $Step END: $StepName - $status =========================&quot;
          $this.WriteLog(&#039;INFO&#039;, $message, $null, 9999, &#039;Main&#039;, $false)
          $this.BlankLine()
     }

}
</pre></div></details>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading" style="margin-top:var(--wp--preset--spacing--60);margin-bottom:var(--wp--preset--spacing--40)">Zusammenfassung</h2>



<p>PowerShell Klassen revolutionieren die Art, wie du größere Projekte strukturierst. Seit PowerShell 5.0 verfügbar, bieten sie objektorientierte Programmierung mit Eigenschaften, Methoden, Konstruktoren und Method Overloading. Der anfängliche Mehraufwand zahlt sich durch sauberen, wartbaren Code aus &#8211; besonders bei Modulen und komplexeren Funktionssammlungen.</p>



<p><strong>Die wichtigsten Vorteile auf einen Blick</strong></p>



<ul class="wp-block-list">
<li>Stark typisierte Properties mit Validierung</li>



<li>gekapselte Funktionalität durch Methoden</li>



<li>elegante Überladung statt komplexer Parameter-Logik und eine Struktur, die auch nach Monaten noch verständlich bleibt</li>
</ul>



<p>Für Daily-Doing-Scripts bleibt der klassische Ansatz praktischer, aber sobald Wiederverwendbarkeit und Struktur wichtig werden, führt für mich kein Weg mehr an Klassen vorbei. 🫡</p>



<div style="height:55px" aria-hidden="true" class="wp-block-spacer"></div>


<figure style="width:500px;height:500px;" class="wp-block-post-featured-image"><img loading="lazy" decoding="async" width="256" height="256" src="https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="PowerShell meets Classes" style="height:500px;object-fit:cover;" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes.png 256w, https://dbavonnebenan.de/wp-content/uploads/2025/06/logo-powershell-meets-classes-150x150.png 150w" sizes="auto, (max-width: 256px) 100vw, 256px" /></figure><p>The post <a href="https://dbavonnebenan.de/powershell-classes-for-beginner-de/">PowerShell Klassen für Anfänger – Strukturierter Code statt Funktions-Chaos</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/powershell-classes-for-beginner-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Dokumentation von Blocks und Deadlocks &#8211; Wenn der 3rd-Party Vendor Beweise fordert</title>
		<link>https://dbavonnebenan.de/reporting-sql-server-blocks-de/</link>
					<comments>https://dbavonnebenan.de/reporting-sql-server-blocks-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 28 Apr 2025 18:11:00 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[BLOCK]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[DEADLOCK]]></category>
		<category><![CDATA[REPORTING]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=411</guid>

					<description><![CDATA[<p>Zu wissen, dass SQL Server Blocking- oder Deadlock-Probleme hat, ist noch ziemlich einfach. Man merkt das normalerweise in der Anwendung und mit den richtigen Queries kann man auch die entsprechenden Blocking-Ketten anzeigen. Am einfachsten geht das mit http://whoisactive.com. Ich hab auch noch eine eigene Entwicklung dazu, die werde ich in einem späteren Blogpost vorstellen, die [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-de/">Dokumentation von Blocks und Deadlocks – Wenn der 3rd-Party Vendor Beweise fordert</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Zu wissen, dass SQL Server Blocking- oder Deadlock-Probleme hat, ist noch ziemlich einfach. Man merkt das normalerweise in der Anwendung und mit den richtigen Queries kann man auch die entsprechenden Blocking-Ketten anzeigen. Am einfachsten geht das mit <a href="http://whoisactive.com">http://whoisactive.com</a>.</p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
SELECT 
    blocking_session_id   AS blocking_session,
    session_id            AS blocked_session,
    wait_time / 1000.0    AS waiting_sec,
    wait_type             AS waittype,
    resource_description  AS ressource
FROM 
    sys.dm_os_waiting_tasks
WHERE 
    blocking_session_id IS NOT NULL
ORDER BY 
    wait_time DESC
</pre></div>


<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Ich hab auch noch eine eigene Entwicklung dazu, die werde ich in einem späteren Blogpost vorstellen, die meinen individuellen Anforderungen besser entspricht.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Das Problem</h2>



<p>Jetzt kann ich AdHoc Blockings anzeigen, aber bei Deadlocks wird&#8217;s interessanter. Dafür muss die System Health Session gecheckt werden, die ist auch noch flüchtig.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Query System Health Session for Deadlocks

SELECT
    event_data.value(&#039;(event/@timestamp)&#x5B;1]&#039;, &#039;datetime2&#039;) AS DeadlockDateTime,
    event_data.query(&#039;.&#039;)                                  AS DeadlockGraph
FROM
(
    SELECT 
        CAST(target_data AS XML) AS target_data
    FROM 
        sys.dm_xe_session_targets AS st
    JOIN 
        sys.dm_xe_sessions AS s ON s.address = st.event_session_address
    WHERE
             s.name = &#039;system_health&#039;
        AND st.target_name = &#039;ring_buffer&#039;
) AS data
CROSS APPLY
    target_data.nodes(&#039;//RingBufferTarget/event&#x5B;@name=&quot;xml_deadlock_report&quot;]&#039;) AS XEventData(event_data)
ORDER BY 
    DeadlockDateTime DESC;
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Dieses Problem hatte ich bei einem Kunden, der extrem mit Deadlocks in einer 3rd Party Anwendung zu kämpfen hatte, aber der Support war bisschen&#8230; sagen wir mal zurückhaltend. 😉</p>



<p>Wir mussten denen alles transparent melden, damit die anfangen eine Lösung zu finden. Query-Resultsets kann man natürlich als transparent betrachten, wenn man sie versteht oder verstehen will. Leider sind die XML von Health Sessions oder Extended Events nicht gerade dafür berühmt, lesbar zu sein. Der Kunde wollte aber einfach nur, dass das Problem gelöst wird.</p>



<p>Also haben wir gemeinsam eine Reporting-Lösung entwickelt.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Die Idee</h2>



<p>Mit Extended Events hab ich XML-Daten und mit PowerShell kann ich XML parsen wie ich will, auch zu einem Report meiner Wahl.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Das Ziel</h2>



<p>Wir wollten eine Möglichkeit, abseits von SQL Server Resultsets, Anwendungsprobleme mit Anomalien in den SQL Server Metriken zu überlagern, um schnell Lösungen herbeizuführen.</p>



<p>In der Woche nach dem Livegang der Lösung gab&#8217;s viele, viele &#8222;Aha&#8220;-Momente. Von falsch konfigurierten ERP-Parametern bis hin zu schlechtem Transaction-Handling.</p>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Der Weg</h2>



<h3 class="wp-block-heading">Persistente Daten</h3>



<p>Das Wichtigste ist, Blocking Session und Deadlock Events persistent zu speichern. Das geht perfekt mit Extended Events. Dafür gibt&#8217;s zwei Bereiche:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- SNIPPET!!

ADD EVENT sqlserver.blocked_process_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
),
ADD EVENT sqlserver.xml_deadlock_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
)
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Das ganze wird in einem eigenen Extended Event konfiguriert und die Aufzeichnungen laufen bereits. <strong>Fast!</strong> Wir müssen noch SQL Server konfigurieren, damit Blocked Process als Extended Events behandelt werden.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
EXEC sp_configure &#039;show advanced options&#039;, 1;
GO
RECONFIGURE;
GO
EXEC sp_configure &#039;blocked process threshold&#039;, &#039;5&#039;; -- 5 Sekunden
RECONFIGURE
GO
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>Daten auslesen</strong></h3>



<h3 class="wp-block-heading">100% PowerShell</h3>



<p>Nachdem die Events geloggt werden, müssen sie ausgewertet werden. Was mir sofort eingefallen ist, waren die dbatools mit <a href="https://docs.dbatools.io/Read-DbaXEFile.html">Read-DbaXEFile</a>. Das Problem ist, dass die aktuell geschriebene Datei damit nicht gelesen werden kann. Du kannst dir vorstellen, dass Blocking- und Deadlock-Events nicht in solchen Massen auftreten, dass dutzende File-Rotationen innerhalb von 24h durchgeführt werden. Falls doch, hast du definitiv ein anderes Problem als es nur zu reporten. Also haben wir den Umweg über SQL Server genommen, da auch hier die aktuelle Datei gelesen werden kann.</p>



<h3 class="wp-block-heading">PowerShell + TSql</h3>



<p>XML mit Queries zu lesen, fällt mir immer noch sehr schwer, aber DANKE an Brent Ozar, uns allen bekannt, für die <a href="https://www.brentozar.com/archive/2014/03/extended-events-doesnt-hard/">grundlegende Query</a>, auf der ich aufbauen konnte. Zumindest hatte ich jetzt ein Resultset zur Orientierung und ein separates XML für jedes Event im Log.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Snippet
select
    *
FROM sys.fn_xe_file_target_read_file
   (&#039;C:\temp\XEventSessions\blocked_process*.xel&#039;,
    &#039;C:\temp\XEventSessions\blocked_process*.xem&#039;,
     null, null)
CROSS APPLY 
    (SELECT CAST(event_data AS XML) AS event_data) as xevents
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Finalisierung mit PowerShell</h3>



<p>Da ich endlich mein XML-Event hatte, konnte ich es mit PowerShell parsen, transformieren und letztendlich in einen schönen Text-Report verpacken.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Sql Server Deadlock Analysis | Event Time: 2025-04-08T11:16:07.447
================================================================================

SQL STATEMENTS INVOLVED
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

    Process ID:   process258294fbc28
    SPID:         650
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

VICTIM LIST
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME


PROCESS LIST
--------------------------------------------------------------------------------

    Process ID:               process261b00cfc28
    SPID:                     643
    Isolation Level:          read uncommitted (1)
    Logical Reads:            700
    Wait Resource:            KEY: 6:72058005407662080 (fb04e1bc7788)
    Wait Time (ms):           2247
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.447
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:34784&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


    Process ID:               process258294fbc28
    SPID:                     650
    Isolation Level:          read uncommitted (1)
    Logical Reads:            1904
    Wait Resource:            KEY: 6:72057996178358272 (2a9726d2d061)
    Wait Time (ms):           2278
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.330
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:11664&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


RESOURCE LIST
--------------------------------------------------------------------------------

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprmp300100
        Index:              Itcprmp300100_1a
        Lock Mode:          X
        HOBT ID:            72058005407662080
        Associated Object ID: 72058005407662080

    Owners:
        Process ID:         process258294fbc28, Mode: X

    Waiting Processes:
        Process ID:         process261b00cfc28, Mode: U, Request Type: wait

================================================================================

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprrp011100
        Index:              Itcprrp011100_1a
        Lock Mode:          X
        HOBT ID:            72057996178358272
        Associated Object ID: 72057996178358272

    Owners:
        Process ID:         process261b00cfc28, Mode: X

    Waiting Processes:
        Process ID:         process258294fbc28, Mode: U, Request Type: wait

================================================================================
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Umsetzung</h3>



<p>Es wurde gewünscht, die Ergebnisse in den Daily Report zu integrieren, den wir bereits mit PowerShell umgesetzt hatten. Jetzt ist es nicht geil, potenziell jeden Morgen 200 Textdateien an eine E-Mail zu hängen, deshalb gibt das PowerShell-Script eine Tabelle zurück, die zeigt, wie Blocks und Locks in Zeitraum X aufgetreten sind, und bei Bedarf kann man proaktiv die Ordnerstruktur checken.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="662" height="223" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png" alt="" class="has-border-color has-base-border-color wp-image-261" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png 662w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33-300x101.png 300w" sizes="auto, (max-width: 662px) 100vw, 662px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Für den dramatischen Effekt und schnellen Überblick wird auch noch eine Excel-Datei generiert. In diesem Beispiel gab&#8217;s einen schönen Kollateralfund, dass die DWH-Jobs massive Probleme hatten 🙂</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large has-custom-border is-style-default"><img loading="lazy" decoding="async" width="1024" height="471" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png" alt="" class="has-border-color wp-image-254" style="border-color:#000000;border-width:1px;border-radius:5px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-300x138.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-768x353.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18.png 1131w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Und schon hatten wir ein paar Reports, die wir dem Vendor in die Hand drücken konnten, und dann ging&#8217;s sehr schnell.</p>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Die Ordnerstruktur

_report/
├── text_reports/
│   └── YYYY/
│       └── MM/
│           └── DD/
│               ├── 20231120_123456_dbname_blocked.txt
│               ├── 20231120_123456_dbname_blocked.xml
│               └── 20231120_123456_dbname_deadlock.txt
└── excel_reports/
    └── BlockReport_20231120_123456.xlsx
</pre></div>


<div style="height:22px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>Leider erlebe ich immer wieder, dass Anwendungsvendoren denken, ihre Kunden sind zu blöd und können die Anwendung nicht bedienen. Hört auf damit und glaubt euren Kunden manchmal auch ohne 101% Daten, die man nur sammeln kann, wenn man bereits Sachkenntnis hat.</strong></p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Repository ansehen</h2>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/get-snapBlockEvents


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Das Script hat verschiedene Parameter und Optionen, Details stehen in der README.MD. Falls du Fragen hast, lass sie in den Kommentaren oder kontaktier mich.</p>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Anderes</h2>



<p>Erik Darling hat auch eine Lösung entwickelt, um Extended Events lesbar zu machen, falls andere Ansätze gebraucht werden. (<a href="https://github.com/erikdarlingdata/DarlingData/tree/main/sp_HumanEvents">sp_HumanEvents</a>)</p>



<div style="height:74px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="295" height="294" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png" alt="" class="wp-image-113" style="width:223px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png 295w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19-150x150.png 150w" sizes="auto, (max-width: 295px) 100vw, 295px" /></figure>



<p class="has-text-align-center"><strong>Gabriel, der DBAvonNebenan</strong></p>



<p></p><p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-de/">Dokumentation von Blocks und Deadlocks – Wenn der 3rd-Party Vendor Beweise fordert</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/reporting-sql-server-blocks-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</title>
		<link>https://dbavonnebenan.de/reporting-sql-server-blocks-en/</link>
					<comments>https://dbavonnebenan.de/reporting-sql-server-blocks-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Mon, 28 Apr 2025 14:19:36 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[Sql Server]]></category>
		<category><![CDATA[BLOCK]]></category>
		<category><![CDATA[DEADLOCK]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[REPORTING]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=253</guid>

					<description><![CDATA[<p>Knowing that SQL Server has blocking or deadlock problems is still quite easy. You usually notice this in the application, and with the right queries, you can also display the corresponding blocking chains. The simplest way to do this is with http://whoisactive.com. I also have a custom development that I will present in a later [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-en/">Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Knowing that SQL Server has blocking or deadlock problems is still quite easy. You usually notice this in the application, and with the right queries, you can also display the corresponding blocking chains. The simplest way to do this is with <a href="http://whoisactive.com">http://whoisactive.com</a>.</p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
SELECT 
    blocking_session_id   AS blocking_session,
    session_id            AS blocked_session,
    wait_time / 1000.0    AS waiting_sec,
    wait_type             AS waittype,
    resource_description  AS ressource
FROM 
    sys.dm_os_waiting_tasks
WHERE 
    blocking_session_id IS NOT NULL
ORDER BY 
    wait_time DESC
</pre></div>


<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<p>I also have a custom development that I will present in a later blog post, which better meets my individual requirements.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Problem</h2>



<p>Now I can display AdHoc blocks, but with deadlocks it gets more interesting. For this, the System Health Session must be checked, which is also volatile.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Query System Health Session for Deadlocks

SELECT
    event_data.value(&#039;(event/@timestamp)&#x5B;1]&#039;, &#039;datetime2&#039;) AS DeadlockDateTime,
    event_data.query(&#039;.&#039;)                                  AS DeadlockGraph
FROM
(
    SELECT 
        CAST(target_data AS XML) AS target_data
    FROM 
        sys.dm_xe_session_targets AS st
    JOIN 
        sys.dm_xe_sessions AS s ON s.address = st.event_session_address
    WHERE
             s.name = &#039;system_health&#039;
        AND st.target_name = &#039;ring_buffer&#039;
) AS data
CROSS APPLY
    target_data.nodes(&#039;//RingBufferTarget/event&#x5B;@name=&quot;xml_deadlock_report&quot;]&#039;) AS XEventData(event_data)
ORDER BY 
    DeadlockDateTime DESC;
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>I encountered this problem with a client who was struggling enormously with deadlocks in a 3rd party application, but the support was a bit&#8230; let&#8217;s say reluctant. 😉</p>



<p>We had to report everything transparently to them so they would start finding a solution. Of course, query result sets can be considered transparent if you understand them or want to understand them. Unfortunately, the XML of Health Sessions or Extended Events are not exactly famous for being readable. Yet the client simply wanted the problem to be fixed.</p>



<p>So together we developed a reporting solution.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Idea</h2>



<p>With Extended Events I have XML data, and with PowerShell I can parse XML however I want, including into a report of my choice.</p>



<div style="height:44px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Goal</h2>



<p>We wanted a way, apart from SQL Server result sets, to overlay application problems with anomalies in the SQL Server metrics to quickly bring about solutions.</p>



<p>In the week after the solution went live, there were many, many &#8222;aha&#8220; moments. From incorrectly configured ERP parameters to poor transaction handling.</p>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">The Way</h2>



<h3 class="wp-block-heading">Persistent Data</h3>



<p>The most important thing is to persistently store blocking session and deadlock events. This works perfectly with Extended Events. There are two areas for this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- SNIPPET!!

ADD EVENT sqlserver.blocked_process_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
),
ADD EVENT sqlserver.xml_deadlock_report(
    ACTION(sqlserver.client_app_name,
           sqlserver.client_hostname,
           sqlserver.database_name)
)
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The whole thing is configured in its own Extended Event and the recordings are already running. <strong>Nearly!</strong> We still need to configure SQL Server to treat Blocked Process as Extended Events.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
EXEC sp_configure &#039;show advanced options&#039;, 1;
GO
RECONFIGURE;
GO
EXEC sp_configure &#039;blocked process threshold&#039;, &#039;5&#039;; -- 5 Secconds
RECONFIGURE
GO
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>Read Data</strong></h3>



<h3 class="wp-block-heading">100% PowerShell</h3>



<p>After the events are logged, they need to be evaluated. What immediately came to my mind were the dbatools with <a href="https://docs.dbatools.io/Read-DbaXEFile.html">Read-DbaXEFile</a>. The problem is that the currently written file cannot be read with it. You can imagine that blocking and deadlock events don&#8217;t occur in such masses that dozens of file rotations are performed within 24 hours. If they do, you definitely have a different problem than just reporting it . So we took the detour via SQL Server, since the current file can also be read here.</p>



<h3 class="wp-block-heading">PowerShell + TSql</h3>



<p>I still find it very difficult to read XML with queries, but THANKS to Brent Ozar, known to all of us, for the<a href="https://www.brentozar.com/archive/2014/03/extended-events-doesnt-hard/"> basic query</a> I could build upon. At least I now had a result set for orientation and a separate XML for each event in the log.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: sql; title: ; notranslate">
-- Snippet
select
    *
FROM sys.fn_xe_file_target_read_file
   (&#039;C:\temp\XEventSessions\blocked_process*.xel&#039;,
    &#039;C:\temp\XEventSessions\blocked_process*.xem&#039;,
     null, null)
CROSS APPLY 
    (SELECT CAST(event_data AS XML) AS event_data) as xevents
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Finalization with PowerShell</h3>



<p>Since I finally had my XML event, I could parse it with PowerShell, transform it, and finally package it into a nice text report.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Sql Server Deadlock Analysis | Event Time: 2025-04-08T11:16:07.447
================================================================================

SQL STATEMENTS INVOLVED
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

    Process ID:   process258294fbc28
    SPID:         650
    Database:     DATABASENAME
    Query:        **DETAILED QUERY**

----------------------------------------

VICTIM LIST
--------------------------------------------------------------------------------

    Process ID:   process261b00cfc28
    SPID:         643
    Database:     DATABASENAME


PROCESS LIST
--------------------------------------------------------------------------------

    Process ID:               process261b00cfc28
    SPID:                     643
    Isolation Level:          read uncommitted (1)
    Logical Reads:            700
    Wait Resource:            KEY: 6:72058005407662080 (fb04e1bc7788)
    Wait Time (ms):           2247
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.447
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:34784&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


    Process ID:               process258294fbc28
    SPID:                     650
    Isolation Level:          read uncommitted (1)
    Logical Reads:            1904
    Wait Resource:            KEY: 6:72057996178358272 (2a9726d2d061)
    Wait Time (ms):           2278
    Transaction:              user_transaction
    Last Transaction Started: 2025-04-08T11:16:07.330
    Client:                   &#039;dbserver_dll &amp;lt;ID@APPSERVER:11664&gt;&#039;
    Hostname:                 APPSERVER
    User:                     USERNAME


EXECUTED SQL STATEMENT
----------------------------------------

    **DETAILED QUERY**


Process Details
================================================================================


RESOURCE LIST
--------------------------------------------------------------------------------

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprmp300100
        Index:              Itcprmp300100_1a
        Lock Mode:          X
        HOBT ID:            72058005407662080
        Associated Object ID: 72058005407662080

    Owners:
        Process ID:         process258294fbc28, Mode: X

    Waiting Processes:
        Process ID:         process261b00cfc28, Mode: U, Request Type: wait

================================================================================

    Resource:
        Type:               keylock
        Database:           6
        Object:             DATABASENAME.dbo.tcprrp011100
        Index:              Itcprrp011100_1a
        Lock Mode:          X
        HOBT ID:            72057996178358272
        Associated Object ID: 72057996178358272

    Owners:
        Process ID:         process261b00cfc28, Mode: X

    Waiting Processes:
        Process ID:         process258294fbc28, Mode: U, Request Type: wait

================================================================================
</pre></div>


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Implementation</h3>



<p>It was requested to add the results to the daily report, which we had already implemented with PowerShell. Now, it&#8217;s not great to potentially attach 200 text files to an email every morning, so the PowerShell script returns a table showing how blocks and locks occurred in time period X, and if needed, one can proactively check the folder structure.</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full has-custom-border"><img loading="lazy" decoding="async" width="662" height="223" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png" alt="" class="has-border-color has-base-border-color wp-image-261" style="border-width:1px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33.png 662w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-28_15-33-300x101.png 300w" sizes="auto, (max-width: 662px) 100vw, 662px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>For dramatic effect and a quick overview, an Excel file is also generated. In this example, there was a nice collateral finding that the DWH jobs had serious problems 🙂</p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large has-custom-border is-style-default"><img loading="lazy" decoding="async" width="1024" height="471" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png" alt="" class="has-border-color wp-image-254" style="border-color:#000000;border-width:1px;border-radius:5px" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-1024x471.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-300x138.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18-768x353.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-27_17-18.png 1131w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>And just like that, we had a few reports that we could hand over to the vendor, and then things moved very quickly.</p>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Die Ordner Struktur

_report/
├── text_reports/
│   └── YYYY/
│       └── MM/
│           └── DD/
│               ├── 20231120_123456_dbname_blocked.txt
│               ├── 20231120_123456_dbname_blocked.xml
│               └── 20231120_123456_dbname_deadlock.txt
└── excel_reports/
    └── BlockReport_20231120_123456.xlsx
</pre></div>


<div style="height:22px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><strong>Unfortunately, I experience again and again that application vendors think their customers are stupid and cannot operate the application. Stop doing that and believe your customers sometimes without 101% data, which you can only collect if you already have knowledge of the subject matter.</strong></strong></p>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">See Repository</h2>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>


https://github.com/gabrielkoehl/get-snapBlockEvents


<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<p>The script has various parameters and options, details are in the README.MD. If you have questions, leave them in the comments or contact me.</p>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Other</h2>



<p>Erik Darling has also developed a solution to make Extended Events readable, in case other approaches are needed. (<a href="https://github.com/erikdarlingdata/DarlingData/tree/main/sp_HumanEvents">sp_HumanEvents</a>)</p>



<div style="height:74px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="295" height="294" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png" alt="" class="wp-image-113" style="width:223px;height:auto" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19.png 295w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-21_16-19-150x150.png 150w" sizes="auto, (max-width: 295px) 100vw, 295px" /></figure>



<p class="has-text-align-center"><strong>Gabriel, der DBAvonNebenan</strong></p>



<p></p><p>The post <a href="https://dbavonnebenan.de/reporting-sql-server-blocks-en/">Reporting Blocks and Deadlocks: When the 3rd-Party Vendor Demands Evidence</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/reporting-sql-server-blocks-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Offline Images with Obsidian Website Clipper</title>
		<link>https://dbavonnebenan.de/improve-obsidian-website-scraper-en/</link>
					<comments>https://dbavonnebenan.de/improve-obsidian-website-scraper-en/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Fri, 25 Apr 2025 06:36:48 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[eng]]></category>
		<category><![CDATA[obsidian]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=165</guid>

					<description><![CDATA[<p>The Obsidian website clipper works really well. But I&#8217;m bothered that images only appear as links to the web. This doesn&#8217;t match my idea of a local dump. So I wrote a small PowerShell script that searches through MD files in the current folder for links, downloads them to the attachments folder, and dynamically replaces [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/improve-obsidian-website-scraper-en/">Offline Images with Obsidian Website Clipper</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>The <a href="https://obsidian.md/clipper">Obsidian website clipper </a>works really well. But I&#8217;m bothered that images only appear as links to the web. This doesn&#8217;t match my idea of a local dump.</p>



<p>So I wrote a small PowerShell script that searches through MD files in the current folder for links, downloads them to the attachments folder, and dynamically replaces the links in the MD with local links.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="240" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-1024x240.png" alt="" class="wp-image-234" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-1024x240.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-300x70.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-768x180.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40.png 1065w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:23px" aria-hidden="true" class="wp-block-spacer"></div>


https://gist.github.com/gabrielkoehl/c99b084c85521bcbb533994046e7aa7e


<p></p><p>The post <a href="https://dbavonnebenan.de/improve-obsidian-website-scraper-en/">Offline Images with Obsidian Website Clipper</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/improve-obsidian-website-scraper-en/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Offline Bilder mit Obsidian Website Clipper</title>
		<link>https://dbavonnebenan.de/improve-obsidian-website-scraper-de/</link>
					<comments>https://dbavonnebenan.de/improve-obsidian-website-scraper-de/#respond</comments>
		
		<dc:creator><![CDATA[Gabriel]]></dc:creator>
		<pubDate>Thu, 24 Apr 2025 14:58:59 +0000</pubDate>
				<category><![CDATA[PowerShell]]></category>
		<category><![CDATA[DE]]></category>
		<category><![CDATA[obsidian]]></category>
		<guid isPermaLink="false">https://dbavonnebenan.de/?p=365</guid>

					<description><![CDATA[<p>Der Obsidian Website Clipper funktioniert echt gut. Aber es stört mich, dass Bilder nur als Links zum Web erscheinen. Das passt nicht zu meiner Vorstellung von einem lokalen Dump. Deshalb habe ich ein kleines PowerShell-Script geschrieben, das MD-Dateien im aktuellen Ordner nach Links durchsucht, sie in den Attachments-Ordner herunterlädt und die Links in den MD-Dateien [&#8230;]</p>
<p>The post <a href="https://dbavonnebenan.de/improve-obsidian-website-scraper-de/">Offline Bilder mit Obsidian Website Clipper</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Der <a href="https://obsidian.md/clipper">Obsidian Website Clipper</a> funktioniert echt gut. Aber es stört mich, dass Bilder nur als Links zum Web erscheinen. Das passt nicht zu meiner Vorstellung von einem lokalen Dump.</p>



<p>Deshalb habe ich ein kleines PowerShell-Script geschrieben, das MD-Dateien im aktuellen Ordner nach Links durchsucht, sie in den Attachments-Ordner herunterlädt und die Links in den MD-Dateien dynamisch durch lokale Links ersetzt.<br></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="240" src="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-1024x240.png" alt="" class="wp-image-234" srcset="https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-1024x240.png 1024w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-300x70.png 300w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40-768x180.png 768w, https://dbavonnebenan.de/wp-content/uploads/2025/04/2025-04-25_14-40.png 1065w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:23px" aria-hidden="true" class="wp-block-spacer"></div>


https://gist.github.com/gabrielkoehl/c99b084c85521bcbb533994046e7aa7e


<p></p><p>The post <a href="https://dbavonnebenan.de/improve-obsidian-website-scraper-de/">Offline Bilder mit Obsidian Website Clipper</a> first appeared on <a href="https://dbavonnebenan.de">DBA von Nebenan</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://dbavonnebenan.de/improve-obsidian-website-scraper-de/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
