<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-01-14T23:18:29+03:00</updated><id>/feed.xml</id><title type="html">Abdulaziz’s Personal Blog</title><subtitle>A personal blog where I talk about various technical topics in IT</subtitle><entry><title type="html">Directory Services (Exposition)</title><link href="/activedirectory/2026/01/14/DirectoryServices-Jo.html" rel="alternate" type="text/html" title="Directory Services (Exposition)" /><published>2026-01-14T23:15:37+03:00</published><updated>2026-01-14T23:15:37+03:00</updated><id>/activedirectory/2026/01/14/DirectoryServices-Jo</id><content type="html" xml:base="/activedirectory/2026/01/14/DirectoryServices-Jo.html"><![CDATA[<p>New year, renewed drive to post. Trying a different writing approach this time. Enjoy!</p>

<p>You have heard of Microsoft Active Directory, but why is it called a “directory”? And why is the L in its LDAP protocol “lightweight”? You’ll have to get comfortable because we’re taking a long trip - plus detours - in the short history of Information Technology and the patterns it follows (keep in mind I’m not the best historian). Innovations in software usually take inspiration from inventions that were already useful in the physical world, and these inventions usually come into being to overcome limitations. Over the years, these innovations may become common knowledge, they may become obsolete and forgotten, or they may even become vestiges of what they once were or otherwise get adapted to answer newer necessities, making its original story esoteric knowledge!</p>

<p>Let’s start with phones. As the number of land lines increased, the complexity of phone networks increased, making one-to-one direct phone connections no longer feasible, thus came the first phone exchanges that had switchboards which were manually operated by phone operators. Subscribers to the phone service were initially identified by their name, so one had to inform the operator about who they wanted to call. Then the idea of identifying a subscriber by a short unique number came. Then as the number of telephone exchanges increased, the idea of identifying an exchange by a unique name familiar to the locals also appeared, and the first few letters of that name would become part of the “number” you’re trying to reach, assuming it subscribed to another provider. Eventually switchboards got automated rendering operators redundant, making knowing the correct phone number even more important. This was also around the time the rotary phone dial started having letters added above its numbers. (Detour: Each digit of a rotary phone sent a number of “pulses” equal to the digit being dialed, this made dialing numbers with higher digits slower. Newer digital phones had the “tones” that we are familiar with, which both unified and shortened dial times. Some digital phones - and also modems - had a setting to change between pulse and tone modes for backwards compatibility with older providers).</p>

<p>That’s when the use of books showing the numbers of all subscribers of an area became more common. These phone “directories” could be found within phone booths or sold separately. They also had white pages for residential entries, and yellow pages for paid entries - essentially ads for business - showing both numbers and addresses, but phone directories weren’t the first type of directory to be made; city directories listing businesses were already a thing before the invention of phones, however we’ll end up following a long chain of what-inspired-what if we talked about those.</p>

<p>These physical directories brought a wealth of information, but printed paper cannot be updated easily. This is why the notion of digitized directories was the next logical step, but phones weren’t the main catalyst here, it was postal mail! In parallel to phones, we also had paper mail, that got partially superseded by telegrams, then it finally became obsolete thanks to electronic mail (e-mail), but a challenge for e-mail was how to handle the digital version of “addresses” so that e-mails can go through the digital version of the “post office” in a standardized way. That’s when some brilliant people invented the X.400 standard, which had some features that were ahead of its time, but unfortunately this was a point of history where many competing standards for different things were being made, some even liked to call them “wars” (Detour: Anyone old enough to remember the Betacam / VHS video cassette wars? Or even the PAL/NTSC video standards? At least I’m sure we have some who know the pain of 110v vs 220v and the smell of burnt appliances!), and computer networks were no exception. Designers of X.400 put a lot of their early efforts on the OSI protocols as they were meant to become the universal standard of communications, but OSI was incompatible with the already existing TCP/IP, which at the time was a suite of protocols designed for the much smaller ARPANET (the precursor to the Internet). We now know who won that “war”, and the only part of OSI that is still relevant is its 7-layers model of networking.</p>

<p>This is enough for the exposition part of our story/article. Next time we’ll talk about the X.400 e-mail standard, its relation to the X.500 directory services standard, and how it lead to LDAP.</p>]]></content><author><name></name></author><category term="ActiveDirectory" /><summary type="html"><![CDATA[New year, renewed drive to post. Trying a different writing approach this time. Enjoy!]]></summary></entry><entry><title type="html">Processing CSV files using PowerShell</title><link href="/powershell/2025/07/22/processing-csv-files-with-powershell.html" rel="alternate" type="text/html" title="Processing CSV files using PowerShell" /><published>2025-07-22T23:46:31+03:00</published><updated>2025-07-22T23:46:31+03:00</updated><id>/powershell/2025/07/22/processing-csv-files-with-powershell</id><content type="html" xml:base="/powershell/2025/07/22/processing-csv-files-with-powershell.html"><![CDATA[<p>In this post we’ll cover two quality-of-life improvements when processing data, one for big data and the other for time stamps.
You may have gotten used to opening CSV files in Excel to do some manual inspection or extra calculations, but noticed when opening these CSVs you sometimes get unexpected behaviors. For example a line of CSV suddenly breaks down to multiple lines with unaligned columns, or when adding a filter to a column that contains what you know to be dates Excel does not treat it as such. This isn’t necessarily a defect in the CSV but more of a side effect when choosing to open CSVs using Excel. In the case of a single row becoming multiple rows of mostly empty columns, it is technically an expected behavior since individual cells in Excel by design have a limit on how much data they hold (exactly 32KiB minus 1 byte) so when a CSV element exceeds that amount then Excel does that behavior. How you resolve this entirely depends on your own needs. If you must preserve this data then you need to find tools other than Excel to perform your CSV processing. Otherwise if you can afford to change this data around then you could use PowerShell to exclude the entire column, the entire row, or just the individual “cells” containing the large data.</p>

<p>I’ll be writing a sample script to pinpoint where the problematic cells are within a given CSV file.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="nv">$ExcelCellLimit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">32</span><span class="n">kb</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span><span class="nv">$CSVFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Import-CSV</span><span class="w"> </span><span class="s2">"C:\Path\To\File.csv"</span><span class="w">
</span><span class="nv">$CSVHeaders</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">gm</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nv">$CSVFile</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">?</span><span class="w"> </span><span class="nx">membertype</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="nt">-expand</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="c">#Get CSV file headers from a sample row</span><span class="w">

</span><span class="c"># Since CSVs can be imagined as 2D arrays, we'll iterate by doing two loops</span><span class="w">
</span><span class="nv">$currentLine</span><span class="o">=</span><span class="mi">1</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$i</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$CSVFile</span><span class="p">){</span><span class="w">
	</span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$j</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$CSVHeaders</span><span class="p">){</span><span class="w">
		</span><span class="nv">$length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$i</span><span class="o">.</span><span class="nv">$j</span><span class="o">.</span><span class="nf">length</span><span class="w"> </span><span class="c"># This is how to dynamically access object properties in PowerShell, by assigning a property name to a variable then using this notation.</span><span class="w">
		</span><span class="kr">if</span><span class="p">(</span><span class="nv">$length</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="nv">$ExcelCellLimit</span><span class="p">){</span><span class="w">
			</span><span class="c"># For this sample script we'll simply report our findings</span><span class="w">
			</span><span class="n">write-host</span><span class="w"> </span><span class="p">(</span><span class="s2">"row {0} column {1} has {2} chars"</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="p">(</span><span class="nv">$currentLine</span><span class="p">,</span><span class="nv">$j</span><span class="p">,</span><span class="nv">$length</span><span class="p">))</span><span class="w"> </span><span class="c"># This way of making strings with -f is called string formatting</span><span class="w">
		</span><span class="p">}</span><span class="w">
	</span><span class="p">}</span><span class="w">
	</span><span class="nv">$currentLine</span><span class="o">++</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>Keep in mind that whatever processing method you choose to do in PowerShell, when saving the results back using Export-CSV remember to account for file encodings as this cmdlet by default uses ASCII. With that we can now talk about the other scenario which is the other behavior that involves misunderstood dates.</p>

<p>From my past experiences the way that Excel decides how to auto-process columns as dates depends on what your Windows’s date format in Region settings is whenever you launch (or relaunch) the first instance of the Excel application, so if the difference is small enough to be handled by changing Windows settings you can close Excel and do just that. If however the date format is more complicated than that then we can do some data processing using .NET’s DateTime data type.</p>

<p>This time our scenario is our CSV file is already open in Excel and everything is fine besides the column containing dates (or a full time stamp that we only need that date part of), and we’d rather fix it quickly instead of interrupting our flow with writing a full CSV processing script. We can do such ad-hoc processing by making use of the clipboard and the Get-Clipboard &amp; Set-clipboard cmdlets (aliases gcb and scb respectively). We’ll also try to make it a one-liner with aliases instead of full cmdlet names since it is a task that came up on the fly, but first we must determine the exact way of processing these date strings. We’ll optimistic first and take a sample date string from the CSV column and see if .NET DateTime parser can convert it. To avoid making this post too long we’ll take the happy route and pick a sample that natively converts. The bad route will mean you’ll need to do some string manipulations using regular expressions (I should make a future post about that topic!) then sending the results back via the clipboard.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># A sample test of converting a string containing a date, a time, and a time zone to a datetime</span><span class="w">
</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span><span class="s2">"2025-07-22T16:52:47+0300"</span></code></pre></figure>

<p>With the test being a success we can proceed to doing a one-liner that will convert the timestamps to a short date.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># first select the cells containing the dates, press Ctrl-C, then run the below</span><span class="w">
</span><span class="n">gcb</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{</span><span class="kr">if</span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">length</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">){([</span><span class="n">datetime</span><span class="p">]</span><span class="bp">$_</span><span class="p">)</span><span class="o">.</span><span class="nf">ToShortDateString</span><span class="p">()}</span><span class="kr">else</span><span class="p">{</span><span class="bp">$_</span><span class="p">}}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">scb</span></code></pre></figure>

<p>Keep in mind that clipboard interactions involving Excel and PowerShell can be unreliable, the best case scenario is: When you press Ctrl-C the cells get highlighted as expected, then when you switch to the PowerShell window and run the one-liner you will see the highlighting disappear from the cells, indicating a successful overwrite of clipboard contents with your results, then you go back to Excel and paste as usual. If the highlight doesn’t disappear you might need to re-run the one-liner, or paste into an application other than Excel such as a notepad.</p>

<p>That’s all for today, and see you next time!</p>]]></content><author><name></name></author><category term="powershell" /><summary type="html"><![CDATA[In this post we’ll cover two quality-of-life improvements when processing data, one for big data and the other for time stamps. You may have gotten used to opening CSV files in Excel to do some manual inspection or extra calculations, but noticed when opening these CSVs you sometimes get unexpected behaviors. For example a line of CSV suddenly breaks down to multiple lines with unaligned columns, or when adding a filter to a column that contains what you know to be dates Excel does not treat it as such. This isn’t necessarily a defect in the CSV but more of a side effect when choosing to open CSVs using Excel. In the case of a single row becoming multiple rows of mostly empty columns, it is technically an expected behavior since individual cells in Excel by design have a limit on how much data they hold (exactly 32KiB minus 1 byte) so when a CSV element exceeds that amount then Excel does that behavior. How you resolve this entirely depends on your own needs. If you must preserve this data then you need to find tools other than Excel to perform your CSV processing. Otherwise if you can afford to change this data around then you could use PowerShell to exclude the entire column, the entire row, or just the individual “cells” containing the large data.]]></summary></entry><entry><title type="html">Joining lists in PowerShell</title><link href="/powershell/2025/07/12/joining-lists-in-powershell.html" rel="alternate" type="text/html" title="Joining lists in PowerShell" /><published>2025-07-12T21:00:15+03:00</published><updated>2025-07-12T21:00:15+03:00</updated><id>/powershell/2025/07/12/joining-lists-in-powershell</id><content type="html" xml:base="/powershell/2025/07/12/joining-lists-in-powershell.html"><![CDATA[<p>This time we’ll talk about an advanced topic in PowerShell, and arguably a demonstration of it being the wrong tool for the job but nevertheless it helps in a pinch!</p>

<p>The example scenario is simple enough, given two lists where one contains employee names and IDs, and the other contains IDs and yearly performance evaluations, you are tasked with correlating employee names with their evaluations.</p>

<p>The trivial solution is to not use PowerShell as this is a simple SQL join question, you just need some time to create appropriate tables, load the data, then perform the join query. But, what if you get such requests frequently and most of the time the data types (and therefore the table column types) are different? You’ll waste time preparing different data models to answer repetitive questions.</p>

<p>We could try attempting to solve this using PowerShell. To simplify implementation and to focus on the point we’re trying to demonstrate, our scenario will have both lists be 10000 entries long with 1:1 cardinality (each row in Names matches exactly 1 row in Evals) and both lists are ordered by ID. The simplest way is to solve this problem is to mimic join operations by doing basic filtering through pipelines as shown below.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># Let's prepare some sample data, instead of coming up with 10000 actual names we'll make random GUIDs.</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Generating Data"</span><span class="w">
</span><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$Names</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">10000</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{[</span><span class="n">pscustomobject</span><span class="p">]@{</span><span class="nx">ID</span><span class="o">=</span><span class="bp">$_</span><span class="p">;</span><span class="nx">Name</span><span class="o">=</span><span class="p">[</span><span class="n">guid</span><span class="p">]</span><span class="err">::</span><span class="nx">NewGuid</span><span class="err">()</span><span class="p">}}</span><span class="w">
</span><span class="nv">$Evals</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">10000</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{[</span><span class="n">pscustomobject</span><span class="p">]@{</span><span class="nx">ID</span><span class="o">=</span><span class="bp">$_</span><span class="p">;</span><span class="nx">Eval</span><span class="o">=</span><span class="nx">Get</span><span class="err">-</span><span class="nx">Random</span><span class="w"> </span><span class="err">-</span><span class="nx">Min</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="err">-</span><span class="nx">Max</span><span class="w"> </span><span class="mi">100</span><span class="p">}}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Performing Joins using a pipeline of ForEach-Object (%) and Where-Object (?)"</span><span class="w">
</span><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$Join</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$Names</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{</span><span class="nv">$Current</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="p">;</span><span class="nv">$Matched</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$Evals</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nf">?</span><span class="w"> </span><span class="nx">ID</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$Current</span><span class="o">.</span><span class="nf">ID</span><span class="p">;[</span><span class="n">pscustomobject</span><span class="p">]@{</span><span class="nx">Name</span><span class="o">=</span><span class="nv">$Current</span><span class="err">.</span><span class="nx">Name</span><span class="p">;</span><span class="nx">Eval</span><span class="o">=</span><span class="nv">$Matched</span><span class="err">.</span><span class="nx">Eval</span><span class="p">}}</span><span class="w"> </span><span class="p">}</span></code></pre></figure>

<p>For those of you who have previous experience in PowerShell, you might already know that the above method to “join” is the slowest because it purely uses the pipelines feature. All solution approaches in this article are surrounded by Measure-Command to show us how long it took to finish executing the joins. The above sample took 15 minutes to finish “joining” our 10k Names with our 10k Evals on a regular PC.</p>

<p>We can do better. Our next approach is to use the Foreach statement to perform our joins.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Performing Joins using Foreach loops"</span><span class="w">
</span><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$Join2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">Foreach</span><span class="p">(</span><span class="nv">$Current</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$Names</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="nv">$Matched</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">Foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$i</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$Evals</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$i</span><span class="o">.</span><span class="nf">ID</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$Current</span><span class="o">.</span><span class="nf">ID</span><span class="p">){</span><span class="nv">$i</span><span class="p">}}</span><span class="w">
</span><span class="p">[</span><span class="n">pscustomobject</span><span class="p">]@{</span><span class="nx">Name</span><span class="o">=</span><span class="nv">$Current</span><span class="err">.</span><span class="nx">Name</span><span class="p">;</span><span class="nx">Eval</span><span class="o">=</span><span class="nv">$Matched</span><span class="err">.</span><span class="nx">Eval</span><span class="p">}}</span><span class="w"> </span><span class="p">}</span></code></pre></figure>

<p>This time the above sample took 4 minutes and 7 seconds, a very noticeable improvement! But stopping here means we haven’t talked anything “advanced” as the article promised, so let’s go deeper!</p>

<p>If you are a seasoned C# developer, you might have looked at these numbers and thought “I can perform these joins in an instant using LINQ and delegates!” and you’d be correct, which should serve as a good reminder to always consider using the right tool for any given task. Still, since both C# and PowerShell have a very close relation with the .NET ecosystem, is there a way to somehow use LINQ in PowerShell ignoring any trade-offs? The answer dear reader is a resounding yes!</p>

<p>Going into the details of how LINQ and delegates work is beyond the scope of this article so it’ll be left as an exercise for the reader, but we’ll be using them in the below sample to do what a C# developer would do to implement a join (though admittedly, LINQ is so integrated with C# syntax that you can port the below sample to C# with much fewer lines of codes and statements, and even have better readability).</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># This custom function should serve as a starting point for doing LINQ in PowerShell, you may need to adapt it to your own specific scenario especially if join attributes are not integers.</span><span class="w">
</span><span class="kr">Function</span><span class="w"> </span><span class="nf">Do-LINQJoin</span><span class="w"> </span><span class="p">(</span><span class="nv">$FirstInput</span><span class="p">,</span><span class="nv">$SecondInput</span><span class="p">,</span><span class="nv">$FirstKey</span><span class="p">,</span><span class="nv">$SecondKey</span><span class="p">){</span><span class="w">
	</span><span class="nv">$FirstKeyDelegate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">iex</span><span class="w"> </span><span class="p">(</span><span class="s2">"[Func[System.Object,</span><span class="si">$(</span><span class="nv">$FirstKey</span><span class="o">.</span><span class="nf">gettype</span><span class="p">()</span><span class="o">.</span><span class="nf">name</span><span class="si">)</span><span class="s2">]] {</span><span class="se">`$</span><span class="s2">args[0].</span><span class="nv">$FirstKey</span><span class="s2"> }"</span><span class="p">)</span><span class="w">
	</span><span class="nv">$SecondKeyDelegate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">iex</span><span class="w"> </span><span class="p">(</span><span class="s2">"[Func[System.Object,</span><span class="si">$(</span><span class="nv">$SecondKey</span><span class="o">.</span><span class="nf">gettype</span><span class="p">()</span><span class="o">.</span><span class="nf">name</span><span class="si">)</span><span class="s2">]] {</span><span class="se">`$</span><span class="s2">args[0].</span><span class="nv">$SecondKey</span><span class="s2"> }"</span><span class="p">)</span><span class="w">
	</span><span class="nv">$InnerJoinDelegate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">Func</span><span class="p">[</span><span class="n">System.Object</span><span class="p">,</span><span class="n">System.Object</span><span class="p">,</span><span class="n">PSCustomObject</span><span class="p">]]</span><span class="w"> </span><span class="p">{</span><span class="w">
		</span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">]@{</span><span class="w">
			</span><span class="nx">FirstInput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w">
			</span><span class="nx">SecondInput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w">
		</span><span class="p">}</span><span class="w">
	</span><span class="p">}</span><span class="w">
	</span><span class="p">[</span><span class="n">Linq.Enumerable</span><span class="p">]::</span><span class="n">Join</span><span class="p">(</span><span class="nv">$FirstInput</span><span class="p">,</span><span class="nv">$SecondInput</span><span class="p">,</span><span class="nv">$FirstKeyDelegate</span><span class="p">,</span><span class="nv">$SecondKeyDelegate</span><span class="p">,</span><span class="nv">$InnerJoinDelegate</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Performing Joins using .NET LINQ"</span><span class="w">
</span><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$Join3i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Do-LINQJoin</span><span class="w"> </span><span class="nv">$Names</span><span class="w"> </span><span class="nv">$Evals</span><span class="w"> </span><span class="s1">'ID'</span><span class="w"> </span><span class="s1">'ID'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Generating results for .NET LINQ"</span><span class="w">
</span><span class="n">Measure-Command</span><span class="w"> </span><span class="p">{</span><span class="w">
	</span><span class="nv">$Join3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$i</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$Join3i</span><span class="p">){[</span><span class="n">pscustomobject</span><span class="p">]@{</span><span class="nx">Name</span><span class="o">=</span><span class="nv">$i</span><span class="err">.</span><span class="nx">FirstInput</span><span class="err">.</span><span class="nx">Name</span><span class="p">;</span><span class="nx">Eval</span><span class="o">=</span><span class="nv">$i</span><span class="err">.</span><span class="nx">SecondInput</span><span class="err">.</span><span class="nx">Eval</span><span class="p">}}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>In the above sample, performing both the “join” procedure between two lists of 10000 items each and generating final objects from that join took us a total of… less than 1 second! To finalize this article let’s compare results for correctness, which should be easy based on how we constructed our sample data.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># Take 10 random indices and check all our relevant objects at the same index position so that we can manually check if results were correct or not, after each random index we print a blue line for readability</span><span class="w">
</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{</span><span class="nv">$c</span><span class="o">=</span><span class="n">get-random</span><span class="w"> </span><span class="nt">-min</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="nt">-max</span><span class="w"> </span><span class="nx">9999</span><span class="p">;</span><span class="nv">$Names</span><span class="p">[</span><span class="nv">$c</span><span class="p">]</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">out-host</span><span class="p">;</span><span class="nv">$Evals</span><span class="p">[</span><span class="nv">$c</span><span class="p">]</span><span class="o">|</span><span class="w"> </span><span class="n">out-host</span><span class="p">;</span><span class="nv">$Join</span><span class="p">[</span><span class="nv">$c</span><span class="p">]</span><span class="o">|</span><span class="w"> </span><span class="n">out-host</span><span class="p">;</span><span class="nv">$Join2</span><span class="p">[</span><span class="nv">$c</span><span class="p">]</span><span class="o">|</span><span class="w"> </span><span class="n">out-host</span><span class="p">;</span><span class="nv">$Join3</span><span class="p">[</span><span class="nv">$c</span><span class="p">]</span><span class="o">|</span><span class="w"> </span><span class="n">out-host</span><span class="p">;</span><span class="n">write-host</span><span class="w"> </span><span class="nt">-back</span><span class="w"> </span><span class="nx">DarkCyan</span><span class="w"> </span><span class="p">(</span><span class="s2">"="</span><span class="o">*</span><span class="mi">30</span><span class="p">)}</span></code></pre></figure>

<p>That’s all for now, and remember to explore more PowerShell and .NET to expand your toolset, but keep in mind it isn’t always the appropriate tool for all scenarios.</p>]]></content><author><name></name></author><category term="powershell" /><summary type="html"><![CDATA[This time we’ll talk about an advanced topic in PowerShell, and arguably a demonstration of it being the wrong tool for the job but nevertheless it helps in a pinch!]]></summary></entry><entry><title type="html">Checking Which Subnet an IP Belongs to in PowerShell</title><link href="/powershell/2025/07/01/powershell-subnets-check.html" rel="alternate" type="text/html" title="Checking Which Subnet an IP Belongs to in PowerShell" /><published>2025-07-01T01:00:00+03:00</published><updated>2025-07-01T01:00:00+03:00</updated><id>/powershell/2025/07/01/powershell-subnets-check</id><content type="html" xml:base="/powershell/2025/07/01/powershell-subnets-check.html"><![CDATA[<p>Today we’ll cover an intermediate PowerShell topic.</p>

<p>In an enterprise IPv4 network, planning subnetting is essential for a corporate environment, but it can lead to scenarios for operation teams where confirming which network does an IP belong to becoming a non-trivial task.</p>

<p>Say for example your network team decided to split a 10.0.0.0/8 subnet into 8 subnets with prefix length of 11, meaning you’d get 10.0.0.0/11, then 10.32.0.0/11 , up to the 8th network of 10.224.0.0/11. Now if for example you receive the IP/subnet pair 10.95.123.65/255.224.0.0, how would you know which subnet it belongs to? For a single example you could simply use an online CIDR tool, or even manually do the math if you feel inclined, but what if you receive a list of hundreds of such pairs?</p>

<p>In such a scenario, you either need to find a tool that does this job, or you could write your own PowerShell script! Starting from around PowerShell 7 (which needs manual installation if not already present), you get access to even more .NET classes, including System.Net.IPNetwork and System.Net.IPAddress. We’ll be making use of those in the sample script below that’ll solve our problem.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="c"># In most scenarios you'd load the list of subnets from a file or another source, in this example we mock it instead</span><span class="w">
</span><span class="nv">$SNs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">7</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{[</span><span class="n">System.Net.IPNetwork</span><span class="p">]</span><span class="s2">"10.</span><span class="si">$(</span><span class="mi">32</span><span class="o">*</span><span class="bp">$_</span><span class="si">)</span><span class="s2">.0.0/11"</span><span class="p">}</span><span class="w">

</span><span class="c"># Generating a random set of 10 IPs that follow the same pattern as our mock subnets</span><span class="w">
</span><span class="nv">$ips</span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">%</span><span class="p">{[</span><span class="n">IPAddress</span><span class="p">]</span><span class="s2">"10.</span><span class="si">$(</span><span class="n">get-random</span><span class="w"> </span><span class="nt">-Maximum</span><span class="w"> </span><span class="nx">256</span><span class="p">)</span><span class="o">.</span><span class="err">$</span><span class="p">(</span><span class="n">get-random</span><span class="w"> </span><span class="nt">-Maximum</span><span class="w"> </span><span class="nx">256</span><span class="p">)</span><span class="o">.</span><span class="err">$</span><span class="p">(</span><span class="n">get-random</span><span class="w"> </span><span class="nt">-Maximum</span><span class="w"> </span><span class="nx">256</span><span class="w"> </span><span class="nt">-Minimum</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="s2">"}

# Loop through the IPs
foreach (</span><span class="nv">$i</span><span class="s2"> in </span><span class="nv">$ips</span><span class="s2">){
	# Get which subnet it belongs to by using the contains method of each subnet
	</span><span class="nv">$sn</span><span class="s2"> = </span><span class="nv">$SNs</span><span class="s2"> | ? {</span><span class="bp">$_</span><span class="s2">.contains(</span><span class="nv">$i</span><span class="s2">)}
	write-host "</span><span class="nv">$i</span><span class="w"> </span><span class="n">belongs</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="nv">$sn</span><span class="s2">"
}</span></code></pre></figure>

<p>Running the above script will show which subnet does our randomly generated IPs belong to.</p>

<p>Another thing we can do with these classes is do lower level calculations, such as determining the network address and broadcast address of each subnet. Admittedly most networks are subnetted in a way that makes knowing these values trivial (last octect is 0 for network and 255 for broadcast). However, if you do run into a case where these addresses are different (e.g. subnets with less than 256 IPs) you will need to use external tools, OR you could do the binary calculations directly in PowerShell! In the below example you’ll find two IPs that at first glance seem to be part of the same subnet since they share the same subnet mask, but because this mask is very small they actually belong to two different subnets.</p>

<figure class="highlight"><pre><code class="language-powershell" data-lang="powershell"><span class="nv">$ip1</span><span class="o">=</span><span class="p">[</span><span class="n">ipaddress</span><span class="p">]</span><span class="s1">'10.224.55.130'</span><span class="w">
</span><span class="nv">$ip2</span><span class="o">=</span><span class="p">[</span><span class="n">ipaddress</span><span class="p">]</span><span class="s1">'10.224.55.87'</span><span class="w">
</span><span class="nv">$snm</span><span class="o">=</span><span class="p">[</span><span class="n">ipaddress</span><span class="p">]</span><span class="s1">'255.255.255.128'</span><span class="w">
</span><span class="n">write-host</span><span class="w"> </span><span class="s2">"Network address of </span><span class="nv">$ip1</span><span class="s2"> :"</span><span class="w"> </span><span class="p">([</span><span class="n">ipaddress</span><span class="p">](([</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$ip1</span><span class="o">.</span><span class="nf">Address</span><span class="w"> </span><span class="o">-band</span><span class="w"> </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$snm</span><span class="o">.</span><span class="nf">Address</span><span class="p">)))</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">
</span><span class="n">write-host</span><span class="w"> </span><span class="s2">"Broadcast address of </span><span class="nv">$ip1</span><span class="s2"> :"</span><span class="w"> </span><span class="p">([</span><span class="n">ipaddress</span><span class="p">]([</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$ip1</span><span class="o">.</span><span class="nf">Address</span><span class="w"> </span><span class="o">-bor</span><span class="w"> </span><span class="nt">-bnot</span><span class="w"> </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$snm</span><span class="o">.</span><span class="nf">Address</span><span class="p">))</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">

</span><span class="n">write-host</span><span class="w"> </span><span class="s2">"Network address of </span><span class="nv">$ip2</span><span class="s2"> :"</span><span class="w"> </span><span class="p">([</span><span class="n">ipaddress</span><span class="p">](([</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$ip2</span><span class="o">.</span><span class="nf">Address</span><span class="w"> </span><span class="o">-band</span><span class="w"> </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$snm</span><span class="o">.</span><span class="nf">Address</span><span class="p">)))</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">
</span><span class="n">write-host</span><span class="w"> </span><span class="s2">"Broadcast address of </span><span class="nv">$ip2</span><span class="s2"> :"</span><span class="w"> </span><span class="p">([</span><span class="n">ipaddress</span><span class="p">]([</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$ip2</span><span class="o">.</span><span class="nf">Address</span><span class="w"> </span><span class="o">-bor</span><span class="w"> </span><span class="nt">-bnot</span><span class="w"> </span><span class="p">[</span><span class="n">uint32</span><span class="p">]</span><span class="nv">$snm</span><span class="o">.</span><span class="nf">Address</span><span class="p">))</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span></code></pre></figure>]]></content><author><name></name></author><category term="powershell" /><summary type="html"><![CDATA[Today we’ll cover an intermediate PowerShell topic.]]></summary></entry><entry><title type="html">Let me sell you PowerShell!</title><link href="/powershell/2025/06/21/let-me-sell-you-powershell.html" rel="alternate" type="text/html" title="Let me sell you PowerShell!" /><published>2025-06-21T00:12:21+03:00</published><updated>2025-06-21T00:12:21+03:00</updated><id>/powershell/2025/06/21/let-me-sell-you-powershell</id><content type="html" xml:base="/powershell/2025/06/21/let-me-sell-you-powershell.html"><![CDATA[<p>Just like how Excel is to data analysis, so is PowerShell to system administration: a ubiquitous tool with limitless potential, for better or worse!</p>

<p>A scripting language at its core, PowerShell gives sysadmins the ability to inquire, control, and manage systems in ways that sometimes rival readymade programs if used by an experienced sysadmin. Need to double check live disk usage of multiple systems? Or list all VMs that raised a specific error during last night’s incident? Maybe even get the mapping between drive letters and WWNs of physical disks zoned to your Windows servers? PowerShell has you covered.</p>

<p>While learning the syntax is important, its real value comes from both the plethora of capabilities that it gets from Modules (custom libraries made by Microsoft or third parties that expose new functions) and its deep integration with .NET classes. Both of these make for an expansive tool chain that can address many of the urgent needs that may come during your work.</p>

<p>Another feature worth mentioning is pipelining, which is taking the output of one command and making it the input of the next one, effectively chaining commands into a pipeline to reach a desired result. While this concept does exist in other scripting tools such as bash, the main difference is that these other tools output pure text thus making further processing limited to direct text manipulation, whereas in PowerShell the output tends to be actual “objects” that give way more options when it comes to manipulating or displaying the results.</p>

<p>All of these PowerShell benefits do come at a cost though. If you think how there exists some Excel files that are handling very complex scenarios to the point where the file is essentially filling the role of a database minus the actual benefits of using a fully fledged database engine, then in a similar vein you can very much end up knowing enough PowerShell to use it in ways that may end up “working” but it won’t scale as the workloads grow with your business. An example of PowerShell gone wrong would be attempting to scan gigabytes of log files containing millions of lines to find occurrences of a specific text using by building a pipeline of commands (get file contents -&gt; match specific pattern -&gt; output result to file). Pipelining in this case will be very slow compared to other approaches that perform better I/O, and while it’s true you could force PowerShell to use those alternative approaches if have the know-how and get good results, it still holds that after a certain complexity threshold you’re better off using compiled programming languages.</p>

<p>PowerShell truly shines in automating any mundane system admin work such user onboarding/offboarding, simplifying a multiple step procedure into a single command that does the steps, and finding data that would otherwise take a long time if a GUI tool was used.</p>

<p>Learning PowerShell (and scripting languages in general) will ensure you stand out in IT.</p>]]></content><author><name></name></author><category term="powershell" /><summary type="html"><![CDATA[Just like how Excel is to data analysis, so is PowerShell to system administration: a ubiquitous tool with limitless potential, for better or worse!]]></summary></entry></feed>