<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: arnostorg</title>
    <description>The latest articles on DEV Community by arnostorg (@arnostorg).</description>
    <link>https://dev.to/arnostorg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1724213%2Fbf22ff52-4243-4658-b3c1-0722d1a13b17.webp</url>
      <title>DEV Community: arnostorg</title>
      <link>https://dev.to/arnostorg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arnostorg"/>
    <language>en</language>
    <item>
      <title>PowerShell Select-Object vs Format-Table: The Real Difference (and Why Your CSV Is Full of GUIDs)</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Fri, 30 Oct 2026 08:24:18 +0000</pubDate>
      <link>https://dev.to/arnostorg/powershell-select-object-vs-format-table-the-real-difference-and-why-your-csv-is-full-of-guids-o1m</link>
      <guid>https://dev.to/arnostorg/powershell-select-object-vs-format-table-the-real-difference-and-why-your-csv-is-full-of-guids-o1m</guid>
      <description>&lt;p&gt;&lt;code&gt;Select-Object&lt;/code&gt; and &lt;code&gt;Format-Table&lt;/code&gt; both let you pick which columns you see, and in the console they look almost identical. Then you pipe the result into &lt;code&gt;Export-Csv&lt;/code&gt; and get a file full of GUIDs instead of file names. That is not a bug in &lt;code&gt;Export-Csv&lt;/code&gt;. It is the one real difference between these two cmdlets, and once you see it you stop making the mistake.&lt;/p&gt;

&lt;p&gt;Everything below was run in PowerShell 7.4.6. The behavior is the same in Windows PowerShell 5.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shortest honest comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Select-Object&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Format-Table&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it outputs&lt;/td&gt;
&lt;td&gt;Real objects with the properties you picked&lt;/td&gt;
&lt;td&gt;Formatting instructions for the host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you sort, filter, or export afterwards?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No, not in any useful way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it belongs in a pipeline&lt;/td&gt;
&lt;td&gt;Anywhere&lt;/td&gt;
&lt;td&gt;Last, or right before &lt;code&gt;Out-File&lt;/code&gt; / &lt;code&gt;Out-String&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical job&lt;/td&gt;
&lt;td&gt;Shape data for the next command&lt;/td&gt;
&lt;td&gt;Make data readable for a human&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you remember one rule, remember the one Microsoft Learn calls &lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/05-formatting-aliases-providers-comparison" rel="noopener noreferrer"&gt;format right&lt;/a&gt;: formatting cmdlets go at the very end of the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: Format-Table before Export-Csv
&lt;/h2&gt;

&lt;p&gt;Here is the mistake almost everyone makes once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Export-Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports\logs.csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoTypeInformation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is what actually lands in the CSV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"
"033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",
"9e210fe47d09416682b841769c78b8a3",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No file names, no sizes. The &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv" rel="noopener noreferrer"&gt;Export-Csv documentation&lt;/a&gt; warns about exactly this: &lt;code&gt;Format-Table&lt;/code&gt; sends table format objects down the pipeline, and &lt;code&gt;Export-Csv&lt;/code&gt; faithfully serializes those instead of your data.&lt;/p&gt;

&lt;p&gt;The fix is one word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Export-Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports\logs.csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoTypeInformation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Name","Length"
"log1.txt","101"
"log2.txt","201"
"log3.txt","301"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-NoTypeInformation&lt;/code&gt; is only needed in Windows PowerShell 5.1. PowerShell 7 leaves the &lt;code&gt;#TYPE&lt;/code&gt; header out by default, and the switch is harmless there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prove it with Get-Member
&lt;/h2&gt;

&lt;p&gt;You do not have to take anyone's word for it. Ask PowerShell what type comes out of each cmdlet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Member&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TypeName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Microsoft.PowerShell.Commands.Internal.Format.FormatStartData&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Member&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TypeName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Selected.System.IO.FileInfo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Select-Object&lt;/code&gt; gives you new objects that still carry &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Length&lt;/code&gt; as real properties. &lt;code&gt;Format-Table&lt;/code&gt; gives you internal formatting records that only the console knows how to draw.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quieter bug: sorting and filtering after Format-Table
&lt;/h2&gt;

&lt;p&gt;The CSV case is loud. These two are silent, which makes them worse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Looks fine, sorts nothing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no error. The table prints in the original order, because &lt;code&gt;Sort-Object&lt;/code&gt; is sorting formatting records that have no &lt;code&gt;Length&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Measure-Object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 0&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Measure-Object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same input, same filter, completely different answer. If a script "finds nothing" and there is a &lt;code&gt;Format-*&lt;/code&gt; anywhere before the filter, that is your first suspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right order: filter, sort, select, then format
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1MB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AutoSize&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it left to right: narrow the data, order it, keep the columns you need, and only then make it pretty. Swap the last line for &lt;code&gt;Export-Csv&lt;/code&gt;, &lt;code&gt;ConvertTo-Json&lt;/code&gt;, or another cmdlet and the pipeline still works, because everything before it is real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculated columns belong in Select-Object too
&lt;/h2&gt;

&lt;p&gt;Both cmdlets accept &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_calculated_properties" rel="noopener noreferrer"&gt;calculated properties&lt;/a&gt;, so it is tempting to build a "SizeKB" column inside &lt;code&gt;Format-Table&lt;/code&gt;. Build it in &lt;code&gt;Select-Object&lt;/code&gt; instead and it survives the export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SizeKB'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Expression&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1KB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Export&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="err"&gt;:\&lt;/span&gt;&lt;span class="nx"&gt;Reports&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kb&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;NoTypeInformation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Name","SizeKB"
"log1.txt","0.1"
"log2.txt","0.2"
"log3.txt","0.29"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Format-Table is exactly right
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Format-Table&lt;/code&gt; is not the villain. It is the right tool whenever a human is the last consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Readable console output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Running'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AutoSize&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Readable text report for a ticket or email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Format-Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AutoSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports\logs.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Out-File&lt;/code&gt; and &lt;code&gt;Out-String&lt;/code&gt; understand formatting records, so a formatted table written to a &lt;code&gt;.txt&lt;/code&gt; file looks exactly like it did on screen. See the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/format-table" rel="noopener noreferrer"&gt;Format-Table reference&lt;/a&gt; for &lt;code&gt;-AutoSize&lt;/code&gt;, &lt;code&gt;-Wrap&lt;/code&gt;, and &lt;code&gt;-GroupBy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Select-Object switches worth knowing
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-object" rel="noopener noreferrer"&gt;Select-Object reference&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Just the values, not objects with one property&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Top N after sorting&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-ExpandProperty&lt;/code&gt; is the one to reach for when the next command wants plain strings, like a list of names to feed into &lt;code&gt;Remove-Item&lt;/code&gt; or &lt;code&gt;Copy-Item&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Next step is a cmdlet, a file format, or another script: use &lt;code&gt;Select-Object&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Next step is your eyes or a &lt;code&gt;.txt&lt;/code&gt; report: use &lt;code&gt;Format-Table&lt;/code&gt;, and put it last.&lt;/li&gt;
&lt;li&gt;Export full of GUIDs, sort that does nothing, or filter that returns zero: look for a &lt;code&gt;Format-*&lt;/code&gt; too early in the pipeline.&lt;/li&gt;
&lt;li&gt;Need a computed column you can export: build it in &lt;code&gt;Select-Object&lt;/code&gt; with a calculated property.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practice the pipeline order hands-on
&lt;/h2&gt;

&lt;p&gt;Reading about pipeline order is easy; getting it right under pressure takes reps. &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master's free interactive PowerShell, CMD, and Bash practice in your browser&lt;/a&gt; lets you run real commands in a browser terminal with no install, so you can drill filter-sort-select-format until it is automatic.&lt;/p&gt;

&lt;p&gt;What is the strangest thing you have found in a CSV that went through &lt;code&gt;Format-Table&lt;/code&gt; first?&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>scripting</category>
      <category>windows</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Windows FINDSTR Command: Find an Error Code Across Every Log File</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Wed, 28 Oct 2026 08:21:29 +0000</pubDate>
      <link>https://dev.to/arnostorg/windows-findstr-command-find-an-error-code-across-every-log-file-3h07</link>
      <guid>https://dev.to/arnostorg/windows-findstr-command-find-an-error-code-across-every-log-file-3h07</guid>
      <description>&lt;p&gt;Something broke overnight, the app wrote forty log files, and all you have is an error code like &lt;code&gt;ERR-504&lt;/code&gt;. You don't need PowerShell, an editor, or a log tool for the first pass. &lt;code&gt;findstr&lt;/code&gt; ships with every Windows install since XP, works in a locked-down &lt;code&gt;cmd.exe&lt;/code&gt;, and can tell you &lt;strong&gt;which files&lt;/strong&gt; contain the code, &lt;strong&gt;which lines&lt;/strong&gt;, and &lt;strong&gt;whether it's there at all&lt;/strong&gt; in one command.&lt;/p&gt;

&lt;p&gt;This post is the version I actually use: search a whole folder tree, get line numbers, list only file names, avoid the space-separated-words trap, and use the exit code in a batch script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-liner
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /n /l /c:"ERR-504" C:\Logs\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each switch does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/s&lt;/code&gt; searches the current folder &lt;strong&gt;and every subfolder&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/i&lt;/code&gt; ignores case, so &lt;code&gt;err-504&lt;/code&gt; and &lt;code&gt;ERR-504&lt;/code&gt; both match.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/n&lt;/code&gt; prints the line number in front of each hit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/l&lt;/code&gt; treats the search text literally (no regex surprises).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/c:"..."&lt;/code&gt; searches for the exact string, spaces included.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Output looks like this, one hit per line, with &lt;code&gt;path:line:text&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Logs\api\2026-10-27.log:1184:2026-10-27 03:12:09 ERR-504 upstream timeout /orders
C:\Logs\api\2026-10-28.log:22:2026-10-28 00:01:44 ERR-504 upstream timeout /orders
C:\Logs\worker\queue.log:907:retry 3/3 failed with ERR-504
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole first-pass triage: you know which service logged it, when, and where to open the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap #1: spaces mean OR
&lt;/h2&gt;

&lt;p&gt;This is the mistake almost everyone makes once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr "upstream timeout" C:\Logs\api\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; search for the phrase &lt;code&gt;upstream timeout&lt;/code&gt;. Without &lt;code&gt;/c:&lt;/code&gt;, findstr splits the text on spaces and matches lines containing &lt;code&gt;upstream&lt;/code&gt; &lt;strong&gt;or&lt;/strong&gt; &lt;code&gt;timeout&lt;/code&gt;. You get far more lines than you expected and you don't notice why.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;/c:&lt;/code&gt; whenever the text has a space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /i /l /c:"upstream timeout" C:\Logs\api\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you &lt;em&gt;do&lt;/em&gt; want OR, the space behavior is actually handy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /i /n "ERR-504 ERR-502 ERR-503" C:\Logs\api\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's every gateway error in one pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap #2: be explicit about literal vs regex
&lt;/h2&gt;

&lt;p&gt;findstr's regex support is small: &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, &lt;code&gt;[class]&lt;/code&gt;, &lt;code&gt;[^class]&lt;/code&gt;, &lt;code&gt;[x-y]&lt;/code&gt;, &lt;code&gt;\&amp;lt;&lt;/code&gt; and &lt;code&gt;\&amp;gt;&lt;/code&gt; for word boundaries. There's no &lt;code&gt;+&lt;/code&gt;, no &lt;code&gt;|&lt;/code&gt;, and no &lt;code&gt;{3}&lt;/code&gt; quantifiers. A dot in your search text is a wildcard unless you're in literal mode.&lt;/p&gt;

&lt;p&gt;So say what you mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:: literal: a dot is just a dot
findstr /s /l /c:"v2.4.1" *.log

:: regex: lines that START with a date like 2026-10-28
findstr /r /n "^2026-10-28" C:\Logs\api\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding &lt;code&gt;/l&lt;/code&gt; or &lt;code&gt;/r&lt;/code&gt; every time costs two characters and removes a whole class of "why does this match?" bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just tell me which files: /m
&lt;/h2&gt;

&lt;p&gt;When the folder has hundreds of logs, you usually want the list of affected files first, not every line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /m /l /c:"ERR-504" C:\Logs\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Logs\api\2026-10-27.log
C:\Logs\api\2026-10-28.log
C:\Logs\worker\queue.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count them by piping into &lt;code&gt;find /c /v ""&lt;/code&gt;, which counts every line it receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /m /l /c:"ERR-504" C:\Logs\*.log | find /c /v ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the hits to a file you can attach to a ticket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /n /l /c:"ERR-504" C:\Logs\*.log &amp;gt; C:\Temp\err-504-hits.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hide the noise: /v
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/v&lt;/code&gt; prints lines that &lt;strong&gt;don't&lt;/strong&gt; match. Combine it with a pipe to strip out the chatter before you search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type C:\Logs\api\2026-10-28.log | findstr /v /i "DEBUG TRACE" | findstr /i /n "ERR-"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;findstr&lt;/code&gt; throws away &lt;code&gt;DEBUG&lt;/code&gt; and &lt;code&gt;TRACE&lt;/code&gt; lines, the second finds the errors in what's left. Note that &lt;code&gt;/n&lt;/code&gt; now numbers lines in the filtered stream, not the original file, so use the earlier one-liner when you need real line numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search many codes from a file: /g:
&lt;/h2&gt;

&lt;p&gt;If the on-call runbook lists a dozen error codes, put them in a text file, one per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERR-502
ERR-503
ERR-504
DB-DEADLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /n /l /g:C:\Runbook\codes.txt C:\Logs\*.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating the list is now an edit to &lt;code&gt;codes.txt&lt;/code&gt;, not to the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the exit code in a script
&lt;/h2&gt;

&lt;p&gt;findstr sets &lt;code&gt;%ERRORLEVEL%&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt; when it finds at least one match and &lt;code&gt;1&lt;/code&gt; when it finds none. That makes it a clean yes/no check in a batch file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;findstr&lt;/span&gt; &lt;span class="na"&gt;/s /i /m /l /c&lt;/span&gt;:&lt;span class="s2"&gt;"ERR-504"&lt;/span&gt; &lt;span class="kd"&gt;C&lt;/span&gt;:\Logs\&lt;span class="o"&gt;*&lt;/span&gt;.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;%errorlevel%&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;504&lt;/span&gt; &lt;span class="kd"&gt;found&lt;/span&gt;. &lt;span class="kd"&gt;Check&lt;/span&gt; &lt;span class="kd"&gt;C&lt;/span&gt;:\Logs &lt;span class="kd"&gt;before&lt;/span&gt; &lt;span class="kd"&gt;closing&lt;/span&gt; &lt;span class="kd"&gt;the&lt;/span&gt; &lt;span class="kd"&gt;incident&lt;/span&gt;.
    &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;No&lt;/span&gt; &lt;span class="kd"&gt;ERR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;504&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;the&lt;/span&gt; &lt;span class="kd"&gt;logs&lt;/span&gt;.
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or as a quick one-liner at the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;findstr /s /i /m /l /c:"ERR-504" C:\Logs\*.log &amp;gt;nul &amp;amp;&amp;amp; echo FOUND || echo clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trap #3: a file that "should" match returns nothing
&lt;/h2&gt;

&lt;p&gt;findstr reads files as single-byte text. If a log was written as &lt;strong&gt;UTF-16&lt;/strong&gt; (for example with &lt;code&gt;&amp;gt;&lt;/code&gt; redirection in Windows PowerShell 5.1, whose default output encoding is UTF-16 LE), every character has a null byte next to it and findstr typically finds nothing, with no error.&lt;/p&gt;

&lt;p&gt;Quick check: open the file in Notepad and look at the encoding in the status bar. If it says UTF-16, either search it from PowerShell instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Select-String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Pattern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ERR-504'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SimpleMatch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or fix the writer so it outputs UTF-8. I wrote up the encoding side of this in &lt;a href="https://dev.to/arnostorg/powershell-out-file-vs-set-content-encoding-is-the-real-difference-m9l"&gt;PowerShell Out-File vs Set-Content: Encoding Is the Real Difference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exact phrase, any case, whole tree, with line numbers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /s /i /n /l /c:"text" *.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any of several words&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /i "word1 word2 word3" *.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only the file names&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /s /i /m /l /c:"text" *.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines that do NOT match&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /v /i "DEBUG" app.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines starting with a pattern&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /r "^2026-10-28" app.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Many codes from a list&lt;/td&gt;
&lt;td&gt;&lt;code&gt;findstr /s /i /l /g:codes.txt *.log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yes/no for a script&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;findstr ... &amp;gt;nul&lt;/code&gt;, then check &lt;code&gt;%errorlevel%&lt;/code&gt; (0 = found)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Official reference: &lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/findstr" rel="noopener noreferrer"&gt;findstr on Microsoft Learn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice it before the incident
&lt;/h2&gt;

&lt;p&gt;Reading switches is easy. Remembering &lt;code&gt;/c:&lt;/code&gt; at 3 AM is the hard part. If you want to build that muscle memory, &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master's free interactive Windows command line course&lt;/a&gt; runs a real terminal in your browser, with hands-on CMD, PowerShell, and Bash drills and instant feedback, so you can practice searching, filtering, and file operations without touching a production box.&lt;/p&gt;

&lt;p&gt;What's the error code you grep for most often? Drop it in the comments.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>commandline</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>PowerShell Get-ChildItem -Filter vs -Include: The Real Difference (and Why -Include Returns Nothing)</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:29:21 +0000</pubDate>
      <link>https://dev.to/arnostorg/powershell-get-childitem-filter-vs-include-the-real-difference-and-why-include-returns-nothing-8ec</link>
      <guid>https://dev.to/arnostorg/powershell-get-childitem-filter-vs-include-the-real-difference-and-why-include-returns-nothing-8ec</guid>
      <description>&lt;p&gt;&lt;code&gt;Get-ChildItem -Path C:\logs -Include *.log&lt;/code&gt; looks correct, runs without an error, and returns nothing. Swap &lt;code&gt;-Include&lt;/code&gt; for &lt;code&gt;-Filter&lt;/code&gt; and the files show up. Same cmdlet, same pattern, different result.&lt;/p&gt;

&lt;p&gt;The two parameters run at different stages. &lt;code&gt;-Filter&lt;/code&gt; goes to the provider, which skips non-matching files while it enumerates. &lt;code&gt;-Include&lt;/code&gt; and &lt;code&gt;-Exclude&lt;/code&gt; run inside PowerShell after the provider has already returned items, and only under certain path conditions. Once you know which stage you're working with, the "returns nothing" bug and the slow scripts both make sense.&lt;/p&gt;

&lt;p&gt;Every command below was run in PowerShell 7.4.6 on Linux. I point out where Windows or Windows PowerShell 5.1 behaves differently, based on Microsoft's docs, because I couldn't test those here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small lab
&lt;/h2&gt;

&lt;p&gt;Run this once. It creates a throwaway folder in your temp directory with a few tricky names: a folder whose name contains &lt;code&gt;.log&lt;/code&gt;, an uppercase &lt;code&gt;.LOG&lt;/code&gt; file, a &lt;code&gt;.log.bak&lt;/code&gt; file, and the classic &lt;code&gt;.htm&lt;/code&gt; / &lt;code&gt;.html&lt;/code&gt; pair.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetTempPath&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'gci-lab'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/archive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/old.log.d"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/app.log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/web.log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/notes.txt"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/error.LOG"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'e'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/archive/2026-09.log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'f'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/archive/2026-09.log.bak"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/report.html"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s1"&gt;'h'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="s2"&gt;/logs/report.htm"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pipe results to &lt;code&gt;ForEach-Object Name&lt;/code&gt; to keep the output short. I don't use &lt;code&gt;-Name&lt;/code&gt; because it changes how &lt;code&gt;-Include&lt;/code&gt; behaves (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  The "returns nothing" gotcha
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.log
error.LOG
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*.log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a bug, and it isn't only a Windows PowerShell 5.1 problem. It happened in pwsh 7.4. The &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem" rel="noopener noreferrer"&gt;Get-ChildItem docs&lt;/a&gt; say that &lt;code&gt;-Include&lt;/code&gt; "is effective only when the command includes the contents of an item, such as &lt;code&gt;C:\Windows\*&lt;/code&gt;". A plain &lt;code&gt;-Path ./logs&lt;/code&gt; refers to the folder itself, not its contents, so nothing passes the include check.&lt;/p&gt;

&lt;p&gt;You can fix it in any of three ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Wildcard path: "the contents of logs"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs/&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# 2. Add -Recurse (trailing * becomes optional)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# 3. -LiteralPath (worked in pwsh 7.4 without * or -Recurse)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.log
error.LOG
web.log

2026-09.log
app.log
error.LOG
web.log

app.log
error.LOG
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-Recurse&lt;/code&gt; version also goes into &lt;code&gt;archive/&lt;/code&gt;. Keep that in mind if you add &lt;code&gt;-Recurse&lt;/code&gt; only to make &lt;code&gt;-Include&lt;/code&gt; work: you also get every subfolder.&lt;/p&gt;

&lt;p&gt;The third fix is something I observed in pwsh 7.4, not documented behavior. On Windows PowerShell 5.1, use &lt;code&gt;-Path ...\*&lt;/code&gt; or &lt;code&gt;-Recurse&lt;/code&gt;. Those two are the forms the docs describe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus trap: &lt;code&gt;-Name&lt;/code&gt; takes a different code path
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"without -Name: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*.log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"with -Name:    "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"LiteralPath -Include *.txt -Name: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*.txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;without -Name: 0
with -Name:    3
LiteralPath -Include *.txt -Name: 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;-Name&lt;/code&gt; added, the "returns nothing" command returned 3 files. The &lt;code&gt;-LiteralPath&lt;/code&gt; command that correctly returned only &lt;code&gt;notes.txt&lt;/code&gt; returned all 8 items once &lt;code&gt;-Name&lt;/code&gt; was added, so the include pattern was ignored. If you test an &lt;code&gt;-Include&lt;/code&gt; command with &lt;code&gt;-Name&lt;/code&gt; and then remove &lt;code&gt;-Name&lt;/code&gt; in your script, you may get different results. Filter on objects first and select &lt;code&gt;Name&lt;/code&gt; at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  What -Filter can and can't do
&lt;/h2&gt;

&lt;p&gt;The docs describe it this way: the filter string is passed to the .NET file-enumeration API, it accepts &lt;strong&gt;one string&lt;/strong&gt;, and only &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; work as wildcards.&lt;/p&gt;

&lt;p&gt;You can't pass more than one pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Filter'. Specified method is not supported.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bracket ranges also don't work. You don't get an error, just no results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'[aw]*.log'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs/&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'[aw]*.log'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
app.log
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-Include&lt;/code&gt; uses PowerShell wildcards (&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards" rel="noopener noreferrer"&gt;about_Wildcards&lt;/a&gt;), so &lt;code&gt;[aw]&lt;/code&gt; matches names starting with &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;w&lt;/code&gt;. &lt;code&gt;-Filter&lt;/code&gt; didn't treat &lt;code&gt;[aw]&lt;/code&gt; as a character range and found nothing.&lt;/p&gt;

&lt;p&gt;Also note that &lt;code&gt;-Filter *.log&lt;/code&gt; returned &lt;code&gt;error.LOG&lt;/code&gt; on Linux, where the file system is case-sensitive. I didn't expect that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows-only quirks
&lt;/h3&gt;

&lt;p&gt;On Windows, &lt;code&gt;-Filter&lt;/code&gt; uses the operating system's file name matching rules, and the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles" rel="noopener noreferrer"&gt;Directory.GetFiles docs&lt;/a&gt; list two old quirks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three-character extensions match longer extensions.&lt;/strong&gt; On .NET Framework (the runtime under Windows PowerShell 5.1), &lt;code&gt;*.xls&lt;/code&gt; also returns &lt;code&gt;book.xlsx&lt;/code&gt;, so &lt;code&gt;*.htm&lt;/code&gt; also returns &lt;code&gt;.html&lt;/code&gt;. The docs label this ".NET Framework only". On .NET 5+ it returns only the exact extension. In my pwsh 7.4 lab, &lt;code&gt;-Filter *.htm&lt;/code&gt; returned only &lt;code&gt;report.htm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8.3 short names are matched too.&lt;/strong&gt; A pattern like &lt;code&gt;*1*.txt&lt;/code&gt; can return &lt;code&gt;longfilename.txt&lt;/code&gt; because its short name is &lt;code&gt;LONGFI~1.TXT&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a 5.1 script that uses &lt;code&gt;-Filter *.htm&lt;/code&gt; deletes or moves files, run it on a test folder first. It may also catch &lt;code&gt;.html&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  -Exclude: two behaviors that catch people
&lt;/h2&gt;

&lt;p&gt;Unlike &lt;code&gt;-Include&lt;/code&gt;, &lt;code&gt;-Exclude&lt;/code&gt; works on a plain folder path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;archive
old.log.d
notes.txt
report.htm
report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it only compares &lt;strong&gt;item names&lt;/strong&gt;. It doesn't skip a subfolder during recursion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-09.log
2026-09.log.bak
app.log
error.LOG
notes.txt
report.htm
report.html
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;archive&lt;/code&gt; folder itself was excluded, but both files inside it were still returned. To skip a subfolder, filter on the path: &lt;code&gt;Where-Object FullName -notlike '*/archive/*'&lt;/code&gt; (use &lt;code&gt;\archive\&lt;/code&gt; on Windows).&lt;/p&gt;

&lt;p&gt;The second one: the docs say exclusions are applied &lt;strong&gt;after&lt;/strong&gt; inclusions, and neither one cares whether an item is a file or a folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;bak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mode  Name
----  ----
----- 2026-09.log
d---- old.log.d
----- app.log
----- error.LOG
----- web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The folder &lt;code&gt;old.log.d&lt;/code&gt; matches &lt;code&gt;*.log*&lt;/code&gt;, so it's included. Adding &lt;code&gt;-File&lt;/code&gt; removes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Exclude&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;bak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-09.log
app.log
error.LOG
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-File&lt;/code&gt; and &lt;code&gt;-Directory&lt;/code&gt; are FileSystem-provider switches. Use one whenever your pattern could match both files and folders. &lt;code&gt;-Filter&lt;/code&gt; has the same problem: &lt;code&gt;-Filter *.log* -Recurse&lt;/code&gt; also returns &lt;code&gt;old.log.d&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much faster is -Filter?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/measure-command" rel="noopener noreferrer"&gt;Measure-Command docs&lt;/a&gt; show &lt;code&gt;-Filter&lt;/code&gt; being much faster on &lt;code&gt;C:\Windows&lt;/code&gt;. Here is the same comparison on a generated tree with 20,000 files, so you can rerun it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.Path&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;GetTempPath&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'gci-perf'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.tmp'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;WriteAllText&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"Files created: "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# warm-up so the first run doesn't pay the cold cache cost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$tests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Filter *.log'&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Include *.log'&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Where-Object Extension'&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.log'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Include *.log, *.csv'&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Filter twice (log + csv)'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'*.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'*.csv'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$runs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Measure-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pscustomobject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;Matches&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;MedianMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$runs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Sort&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Files created: 20000

Test                     Matches MedianMs
----                     ------- --------
Filter *.log                4000       50
Include *.log               4000      428
Where-Object Extension      4000      219
Include *.log, *.csv        8000      487
Filter twice (log + csv)    8000       97
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All runs were on the same 8-core Linux box with a warm cache, median of 5. Some results surprised me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-Filter&lt;/code&gt; was about 8× faster than &lt;code&gt;-Include&lt;/code&gt;&lt;/strong&gt; for the same 4,000 files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-Include&lt;/code&gt; was slower than &lt;code&gt;Get-ChildItem | Where-Object&lt;/code&gt;.&lt;/strong&gt; You might expect a built-in parameter to beat a pipeline filter. Here it took twice as long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running &lt;code&gt;-Filter&lt;/code&gt; twice beat one &lt;code&gt;-Include&lt;/code&gt; with two patterns&lt;/strong&gt; (97 ms vs 487 ms), even though it walks the tree twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expect different absolute numbers on Windows, network shares, and cold caches, but the ranking should be the same, because &lt;code&gt;-Filter&lt;/code&gt; skips non-matching files before PowerShell creates objects for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern I use now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One name pattern → &lt;code&gt;-Filter&lt;/code&gt;&lt;/strong&gt;, plus &lt;code&gt;-File&lt;/code&gt; or &lt;code&gt;-Directory&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Several patterns or &lt;code&gt;[a-z]&lt;/code&gt; ranges → &lt;code&gt;-Include&lt;/code&gt; with &lt;code&gt;-Recurse&lt;/code&gt; or a &lt;code&gt;\*&lt;/code&gt; path.&lt;/strong&gt; When speed matters, narrow first with &lt;code&gt;-Filter&lt;/code&gt; and refine with &lt;code&gt;-Include&lt;/code&gt;. The two can be combined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'a*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'w*'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.log
web.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Anything based on size, age, or path → &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object" rel="noopener noreferrer"&gt;Where-Object&lt;/a&gt;&lt;/strong&gt; after a &lt;code&gt;-Filter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs/app.log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs/web.log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$cutoff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;KB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-lt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cutoff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'AgeDays'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastWriteTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Days&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name    Length AgeDays
----    ------ -------
app.log      2      40
web.log   2049       0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to try these commands without setting up a lab folder, you can &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;practice PowerShell file commands in a free browser terminal&lt;/a&gt; on CMD Master. It also covers CMD and Bash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One pattern, fast&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Filter *.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single string, &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; only; on 5.1, &lt;code&gt;*.htm&lt;/code&gt; also matches &lt;code&gt;.html&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several patterns&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Include *.log, *.csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Needs &lt;code&gt;-Recurse&lt;/code&gt; or &lt;code&gt;path\*&lt;/code&gt;, otherwise 0 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove matches&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Exclude *.bak&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compares names only, still descends into excluded folders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files only / folders only&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-File&lt;/code&gt; / &lt;code&gt;-Directory&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;*.log*&lt;/code&gt; also matches a folder named &lt;code&gt;old.log.d&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Size, date, path logic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Where-Object { ... }&lt;/code&gt; after the pipe&lt;/td&gt;
&lt;td&gt;Narrow with &lt;code&gt;-Filter&lt;/code&gt; first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Names only&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ForEach-Object Name&lt;/code&gt; after the pipe&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-Name&lt;/code&gt; changes how &lt;code&gt;-Include&lt;/code&gt; behaves&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Have you run into any of these, especially the &lt;code&gt;-Name&lt;/code&gt; difference, on Windows PowerShell 5.1? I'd like to compare results.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>scripting</category>
      <category>windows</category>
      <category>beginners</category>
    </item>
    <item>
      <title>PowerShell Compare-Object: Find Missing and Changed Files Between Two Folders</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Fri, 23 Oct 2026 07:24:18 +0000</pubDate>
      <link>https://dev.to/arnostorg/powershell-compare-object-find-missing-and-changed-files-between-two-folders-503m</link>
      <guid>https://dev.to/arnostorg/powershell-compare-object-find-missing-and-changed-files-between-two-folders-503m</guid>
      <description>&lt;p&gt;You copied a folder to a backup drive, a USB stick, or a new laptop. Now you need to know one thing: &lt;strong&gt;is the copy actually the same?&lt;/strong&gt; Not "roughly the same size". The same files, with the same content.&lt;/p&gt;

&lt;p&gt;Windows Explorer can't answer that. &lt;code&gt;Compare-Object&lt;/code&gt; plus &lt;code&gt;Get-FileHash&lt;/code&gt; can, in a short script you can rerun any time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-liner: which file names differ?
&lt;/h2&gt;

&lt;p&gt;Start cheap. Compare just the file names in two folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Compare-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Photos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;D:\Backup\Photos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InputObject        SideIndicator
-----------        -------------
IMG_2041.jpg       =&amp;gt;
IMG_1987.jpg       &amp;lt;=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading the &lt;code&gt;SideIndicator&lt;/code&gt; column:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;=&lt;/code&gt; means the item exists only in the &lt;strong&gt;first&lt;/strong&gt; (reference) set, here &lt;code&gt;C:\Photos&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=&amp;gt;&lt;/code&gt; means it exists only in the &lt;strong&gt;second&lt;/strong&gt; (difference) set, the backup.&lt;/li&gt;
&lt;li&gt;Matching items are hidden. Add &lt;code&gt;-IncludeEqual&lt;/code&gt; to see them marked &lt;code&gt;==&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;IMG_1987.jpg&lt;/code&gt; never made it to the backup, and &lt;code&gt;IMG_2041.jpg&lt;/code&gt; is only on the backup.&lt;/p&gt;

&lt;p&gt;That catches missing files. It does &lt;strong&gt;not&lt;/strong&gt; catch a file that exists on both sides but has different content, like a half-copied video or a document you edited after the backup. For that you need hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real job: compare names &lt;em&gt;and&lt;/em&gt; content, recursively
&lt;/h2&gt;

&lt;p&gt;This script builds a fingerprint for every file (relative path plus SHA256 hash), then compares the two lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$left&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Resolve-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\Photos'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$right&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Resolve-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'D:\Backup\Photos'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Get-FileFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pscustomobject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FullName&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Substring&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;TrimStart&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\'&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nx"&gt;Hash&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;FileHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Algorithm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SHA256&lt;/span&gt;&lt;span class="err"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Hash&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-FileFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-FileFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Compare-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ReferenceObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DifferenceObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why each piece matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;RelPath&lt;/code&gt;&lt;/strong&gt; strips the root, so &lt;code&gt;C:\Photos\2025\a.jpg&lt;/code&gt; and &lt;code&gt;D:\Backup\Photos\2025\a.jpg&lt;/code&gt; both become &lt;code&gt;2025\a.jpg&lt;/code&gt; and can actually match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-Property RelPath, Hash&lt;/code&gt;&lt;/strong&gt; tells &lt;code&gt;Compare-Object&lt;/code&gt; an item is only "equal" when both the path and the content hash match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@(...)&lt;/code&gt;&lt;/strong&gt; forces an array even when a folder is empty. More on that below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Identical files produce no output. What's left is the interesting part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RelPath                Hash          SideIndicator
-------                ----          -------------
2025\IMG_1987.jpg      7427D152...   &amp;lt;=
2025\trip.mp4          3BB2ABB6...   &amp;lt;=
2025\trip.mp4          73CB3858...   =&amp;gt;
IMG_2041.jpg           A4FB6214...   =&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;trip.mp4&lt;/code&gt; shows up &lt;strong&gt;twice&lt;/strong&gt;, once per side with different hashes. That's the signature of a file that exists in both places but changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn it into a readable report
&lt;/h2&gt;

&lt;p&gt;Two rows per changed file is accurate but annoying. Group by path and label each case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Compare-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Group-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Changed'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SideIndicator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Missing in backup'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Only in backup'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pscustomobject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status             File
------             ----
Changed            2025\trip.mp4
Missing in backup  2025\IMG_1987.jpg
Only in backup     IMG_2041.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need to send it to someone or keep a record? Pipe the same result to &lt;code&gt;Export-Csv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Export-Csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\backup-diff.csv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoTypeInformation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An empty report means the two folders match, file for file and byte for byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas that bite on real folders
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. An empty folder throws a terminating error.&lt;/strong&gt; If &lt;code&gt;Get-ChildItem&lt;/code&gt; finds nothing, it returns &lt;code&gt;$null&lt;/code&gt;, and &lt;code&gt;Compare-Object&lt;/code&gt; refuses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot bind argument to parameter 'ReferenceObject' because it is null.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wrapping the call in &lt;code&gt;@(...)&lt;/code&gt; gives you an empty array instead, and the comparison simply reports every file on the other side as missing. That's why the script above uses &lt;code&gt;$a = @(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Comparison is case-insensitive by default.&lt;/strong&gt; &lt;code&gt;Report.docx&lt;/code&gt; and &lt;code&gt;report.docx&lt;/code&gt; count as the same name, which matches how NTFS behaves. If you're comparing against a Linux share or a Git checkout where case matters, add &lt;code&gt;-CaseSensitive&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Hashing big folders is slow, so filter first.&lt;/strong&gt; Hashing reads every byte. For a 200 GB media folder, compare cheap properties first and only hash what's left:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Get-QuickFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'RelPath'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'\'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Compare-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-QuickFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-QuickFingerprint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RelPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A different &lt;code&gt;Length&lt;/code&gt; proves a file changed. The same length doesn't prove it's identical, so run the hash version when you need certainty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Don't compare timestamps to verify a copy.&lt;/strong&gt; &lt;code&gt;CreationTime&lt;/code&gt; resets when a file is copied, and not every copy tool or sync client preserves &lt;code&gt;LastWriteTime&lt;/code&gt;, so a time-based diff can flag thousands of false "changes". Content hashes don't care how the file got there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. It's not a line-by-line text diff.&lt;/strong&gt; &lt;code&gt;Compare-Object (Get-Content a.txt) (Get-Content b.txt)&lt;/code&gt; tells you which lines exist on only one side. It won't give line numbers, and a line that merely moved won't be reported. For code, use &lt;code&gt;git diff&lt;/code&gt;. For "did this config gain or lose a line?", it's perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Names only&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Compare-Object (gci A -Name) (gci B -Name)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show matches too&lt;/td&gt;
&lt;td&gt;add &lt;code&gt;-IncludeEqual&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare on chosen properties&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Property RelPath, Hash&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case-sensitive names&lt;/td&gt;
&lt;td&gt;add &lt;code&gt;-CaseSensitive&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Get-FileHash -LiteralPath $file -Algorithm SHA256&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save report&lt;/td&gt;
&lt;td&gt;`\&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Practice it before you need it
&lt;/h2&gt;

&lt;p&gt;The time you most need this is right after a backup or migration, which is the worst time to learn the syntax. If you want to get the pipeline habit into your fingers first, &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;practice PowerShell pipelines like Get-ChildItem and Compare-Object in a free browser terminal&lt;/a&gt; on CMD Master. No install, and nothing on your real disk gets touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/compare-object" rel="noopener noreferrer"&gt;Compare-Object on Microsoft Learn&lt;/a&gt;: {% raw %}&lt;code&gt;SideIndicator&lt;/code&gt;, &lt;code&gt;-Property&lt;/code&gt;, &lt;code&gt;-IncludeEqual&lt;/code&gt;, &lt;code&gt;-CaseSensitive&lt;/code&gt;, and the &lt;code&gt;$null&lt;/code&gt; terminating error&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash" rel="noopener noreferrer"&gt;Get-FileHash on Microsoft Learn&lt;/a&gt;: hash algorithms and &lt;code&gt;-LiteralPath&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem" rel="noopener noreferrer"&gt;Get-ChildItem on Microsoft Learn&lt;/a&gt;: &lt;code&gt;-Recurse&lt;/code&gt;, &lt;code&gt;-File&lt;/code&gt;, and &lt;code&gt;-Name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/group-object" rel="noopener noreferrer"&gt;Group-Object on Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv" rel="noopener noreferrer"&gt;Export-Csv on Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Related in this series: &lt;a href="https://dev.to/arnostorg/powershell-sort-object-order-files-by-size-date-or-name-in-one-pipeline-15ob"&gt;sort files by size, date, or name with Sort-Object&lt;/a&gt; and &lt;a href="https://dev.to/arnostorg/windows-robocopy-command-mirror-a-folder-without-deleting-the-wrong-files-dm0"&gt;mirror a folder safely with Robocopy&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Learning Windows command line from scratch? &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master's interactive CMD, PowerShell, and Bash lessons&lt;/a&gt; are free and run entirely in your browser.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>scripting</category>
      <category>windows</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Windows Robocopy Command: Mirror a Folder Without Deleting the Wrong Files</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:28:25 +0000</pubDate>
      <link>https://dev.to/arnostorg/windows-robocopy-command-mirror-a-folder-without-deleting-the-wrong-files-dm0</link>
      <guid>https://dev.to/arnostorg/windows-robocopy-command-mirror-a-folder-without-deleting-the-wrong-files-dm0</guid>
      <description>&lt;p&gt;&lt;code&gt;robocopy /MIR&lt;/code&gt; is the fastest way to keep two folders identical on Windows. It is also the fastest way to wipe a backup drive, because a mirror does more than copy. It &lt;strong&gt;deletes everything in the destination that isn't in the source&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post covers the version I'd actually put in a scheduled task: preview it first, exclude what you don't want touched, stop the default retry loop from hanging for days, log every run, and read the exit code properly so &lt;code&gt;1&lt;/code&gt; doesn't get flagged as a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy vs mirror: /E vs /MIR
&lt;/h2&gt;

&lt;p&gt;These two lines look nearly identical. They behave very differently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects D:\Backup\Projects /E
robocopy C:\Projects D:\Backup\Projects /MIR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/E&lt;/code&gt; copies all subfolders, including empty ones. Files that exist only in the destination are &lt;strong&gt;left alone&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/MIR&lt;/code&gt; is &lt;code&gt;/E&lt;/code&gt; plus &lt;code&gt;/PURGE&lt;/code&gt;. Files and folders that exist only in the destination are &lt;strong&gt;deleted&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you deleted &lt;code&gt;old-notes.txt&lt;/code&gt; from &lt;code&gt;C:\Projects&lt;/code&gt; last week, &lt;code&gt;/E&lt;/code&gt; keeps the copy on &lt;code&gt;D:&lt;/code&gt; and &lt;code&gt;/MIR&lt;/code&gt; removes it. That's exactly what you want for a true mirror. It's exactly what you don't want if &lt;code&gt;D:\Backup\Projects&lt;/code&gt; also holds files that were never in the source.&lt;/p&gt;

&lt;p&gt;Rule of thumb: use &lt;code&gt;/E&lt;/code&gt; when the destination is an archive and &lt;code&gt;/MIR&lt;/code&gt; when it's a disposable replica.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: dry run with /L
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/L&lt;/code&gt; lists what robocopy &lt;em&gt;would&lt;/em&gt; do without copying, deleting or touching timestamps. Run it every time you build a new mirror command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects D:\Backup\Projects /MIR /L /NJH /FP /NDL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/L&lt;/code&gt; lists only. Nothing changes on disk.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/FP&lt;/code&gt; shows full paths so you can tell which side a file is on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/NDL&lt;/code&gt; hides directory lines so the file actions stand out.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/NJH&lt;/code&gt; drops the job header.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look for lines marked &lt;code&gt;*EXTRA File&lt;/code&gt;. Those are the files &lt;code&gt;/MIR&lt;/code&gt; will delete from the destination. If that list contains anything you care about, stop and fix the command before you remove &lt;code&gt;/L&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can try the flag-reading habit without risking a real disk in &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master, a free browser terminal for practicing Windows CMD, PowerShell and Bash&lt;/a&gt;. Nothing gets installed and nothing you break is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: exclude what the mirror must not touch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects D:\Backup\Projects /MIR /XD node_modules .git bin obj /XF *.tmp ~$* Thumbs.db /L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/XD&lt;/code&gt; excludes directories by name or path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/XF&lt;/code&gt; excludes files by name or wildcard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Excluded items are skipped in both directions. They aren't copied from the source, and a matching folder that already exists in the destination isn't purged either. That makes &lt;code&gt;/XD&lt;/code&gt; useful for protecting a folder in the destination that shouldn't be mirrored, like a local &lt;code&gt;_archive&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;If someone edits files directly on the destination, add &lt;code&gt;/XO&lt;/code&gt;. It stops robocopy from overwriting a destination file with an &lt;em&gt;older&lt;/em&gt; source copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects D:\Backup\Projects /MIR /XO /L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't make two-way sync safe, because robocopy is still one-directional. It just stops the worst case of an old source copy overwriting newer work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: fix the retry defaults
&lt;/h2&gt;

&lt;p&gt;Robocopy's default retry settings are &lt;strong&gt;1,000,000 retries with a 30 second wait&lt;/strong&gt; between each one. A single locked file (an open Outlook PST, a database, someone's open Excel workbook) can stall a scheduled job for what is effectively forever.&lt;/p&gt;

&lt;p&gt;Set retries explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects D:\Backup\Projects /MIR /R:1 /W:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One retry with a one-second wait is enough for a nightly job. The locked file gets reported as failed, and the next run picks it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: speed it up with /MT
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects \\nas01\backup\Projects /MIR /R:1 /W:1 /MT:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/MT&lt;/code&gt; copies with multiple threads (default 8, range 1–128). It helps most with lots of small files over a network share. On a single spinning disk, high thread counts can make things slower, so test with &lt;code&gt;8&lt;/code&gt; and &lt;code&gt;16&lt;/code&gt; before going higher. Microsoft also recommends sending output to a log file when you use &lt;code&gt;/MT&lt;/code&gt;, which brings us to logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: log every run
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy C:\Projects \\nas01\backup\Projects /MIR /R:1 /W:1 /MT:16 /LOG+:C:\Logs\projects-mirror.log /TEE /NP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/LOG+:&lt;/code&gt; appends to the log. &lt;code&gt;/LOG:&lt;/code&gt; without the plus overwrites it every run, and your history goes with it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/TEE&lt;/code&gt; writes to the console and the log at the same time, so interactive runs still show output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/NP&lt;/code&gt; turns off the per-file percentage counter. Without it the log fills up with &lt;code&gt;10% 20% 30%...&lt;/code&gt; noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The summary block at the end of each run (Copied, Skipped, Mismatch, FAILED, Extras) is what you check when someone asks whether last night's backup worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: read the exit code correctly
&lt;/h2&gt;

&lt;p&gt;This catches a lot of people. Robocopy's exit code is a &lt;strong&gt;bit field&lt;/strong&gt;, not "0 = OK, anything else = error":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Nothing to copy. Source and destination already match.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Files were copied successfully.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Extra files exist in the destination. Nothing was copied.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Files copied and extras were found.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5, 6, 7&lt;/td&gt;
&lt;td&gt;Combinations of copied, mismatched and extra files, with no failures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 and above&lt;/td&gt;
&lt;td&gt;At least one file or folder failed to copy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So &lt;code&gt;%ERRORLEVEL%&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt; means &lt;strong&gt;success with work done&lt;/strong&gt;. A script that does &lt;code&gt;if errorlevel 1 goto :fail&lt;/code&gt; will report a failure every time the backup actually copies something. Task Scheduler does the same thing: "Last Run Result: 0x1" looks like an error but means files were copied.&lt;/p&gt;

&lt;p&gt;Here's a batch wrapper that maps robocopy's codes to a normal 0 or 1 for whatever is calling it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;setlocal&lt;/span&gt;

&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"SRC=C:\Projects"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"DST=\\nas01\backup\Projects"&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"LOG=C:\Logs\projects-mirror.log"&lt;/span&gt;

&lt;span class="nb"&gt;robocopy&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%SRC%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%DST%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="na"&gt;/MIR /R&lt;/span&gt;:1 &lt;span class="na"&gt;/W&lt;/span&gt;:1 &lt;span class="na"&gt;/MT&lt;/span&gt;:16 &lt;span class="na"&gt;/XD &lt;/span&gt;&lt;span class="kd"&gt;node_modules&lt;/span&gt; .git &lt;span class="na"&gt;/XF &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;.tmp &lt;span class="na"&gt;/LOG&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%LOG%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="na"&gt;/TEE /NP
&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"RC=&lt;/span&gt;&lt;span class="nv"&gt;%ERRORLEVEL%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;%RC%&lt;/span&gt; &lt;span class="kd"&gt;GEQ&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;%date%&lt;/span&gt; &lt;span class="nv"&gt;%time%&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nb"&gt;robocopy&lt;/span&gt; &lt;span class="kd"&gt;FAILED&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="kd"&gt;code&lt;/span&gt; &lt;span class="nv"&gt;%RC%&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%LOG%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;%date%&lt;/span&gt; &lt;span class="nv"&gt;%time%&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nb"&gt;robocopy&lt;/span&gt; &lt;span class="kd"&gt;OK&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="kd"&gt;code&lt;/span&gt; &lt;span class="nv"&gt;%RC%&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;%LOG%&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt; &lt;span class="na"&gt;/b &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture &lt;code&gt;%ERRORLEVEL%&lt;/code&gt; into a variable right away. Any command after robocopy, including &lt;code&gt;echo&lt;/code&gt; redirection, can change it before you check it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Swapping source and destination.&lt;/strong&gt; &lt;code&gt;robocopy D:\Backup\Projects C:\Projects /MIR&lt;/code&gt; mirrors the backup &lt;em&gt;onto&lt;/em&gt; your working folder and deletes everything you've created since the last backup. Worse, if the "source" path is an empty or freshly created folder, &lt;code&gt;/MIR&lt;/code&gt; empties the destination. Put the paths in named variables like the script above, and always do a &lt;code&gt;/L&lt;/code&gt; run when you change them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A trailing backslash inside quotes.&lt;/strong&gt; &lt;code&gt;robocopy "C:\My Projects\" "D:\Backup\"&lt;/code&gt; breaks because &lt;code&gt;\"&lt;/code&gt; is read as an escaped quote, so the arguments merge in strange ways. Drop the trailing backslash (&lt;code&gt;"C:\My Projects"&lt;/code&gt;). If you need the root of a drive, leave it unquoted (&lt;code&gt;D:\&lt;/code&gt;), since a drive root has no spaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mirroring from a drive root.&lt;/strong&gt; Copying from the root of a volume (for example &lt;code&gt;E:\&lt;/code&gt;) can leave the destination folder with the Hidden and System attributes, so it seems to "disappear" in Explorer. Mirror a subfolder instead, or clear the attributes afterwards with &lt;code&gt;attrib -h -s D:\Backup\E&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/MIR resets destination folder permissions.&lt;/strong&gt; According to the docs, &lt;code&gt;/MIR&lt;/code&gt; overwrites the destination directory's security settings, while &lt;code&gt;/E /PURGE&lt;/code&gt; keeps them. If the destination share has carefully set ACLs, use &lt;code&gt;/E /PURGE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;/MOV and /MOVE are not mirror options.&lt;/strong&gt; They delete from the &lt;em&gt;source&lt;/em&gt; after copying. Mixing them up with &lt;code&gt;/MIR&lt;/code&gt; while "cleaning up" a command is a fast way to lose the original.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAT/exFAT destinations recopy everything.&lt;/strong&gt; USB drives formatted as FAT store timestamps with 2-second precision, so every file looks changed on every run. Add &lt;code&gt;/FFT&lt;/code&gt; so robocopy allows for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  A mirror command I'd actually schedule
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robocopy "C:\Projects" "\\nas01\backup\Projects" /MIR /XO /R:1 /W:1 /MT:16 /XD node_modules .git bin obj /XF *.tmp ~$* /LOG+:"C:\Logs\projects-mirror.log" /TEE /NP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;/L&lt;/code&gt; first, read the &lt;code&gt;*EXTRA File&lt;/code&gt; lines, then remove &lt;code&gt;/L&lt;/code&gt;, and wrap it in the exit-code script before it goes anywhere near Task Scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official docs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy" rel="noopener noreferrer"&gt;robocopy command reference (Microsoft Learn)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility" rel="noopener noreferrer"&gt;Return codes used by the Robocopy utility (Microsoft Learn)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/if" rel="noopener noreferrer"&gt;if command and errorlevel checks (Microsoft Learn)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Want to build flag-reading muscle memory before you run &lt;code&gt;/MIR&lt;/code&gt; against a real share? &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;Practice Windows CMD commands in CMD Master's interactive browser terminal&lt;/a&gt;. It's free, covers CMD, PowerShell and Bash, and has nothing to install.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>commandline</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>From Tutorial Consumer to Operator: The Skills Gap CMD Master Targets</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Mon, 19 Oct 2026 13:29:06 +0000</pubDate>
      <link>https://dev.to/arnostorg/from-tutorial-consumer-to-operator-the-skills-gap-cmd-master-targets-394a</link>
      <guid>https://dev.to/arnostorg/from-tutorial-consumer-to-operator-the-skills-gap-cmd-master-targets-394a</guid>
      <description>&lt;p&gt;Watching CLI tutorials feels like progress. You nod at PATH diagrams. You pause a video to copy a command. You finish a playlist titled “Windows Command Line Full Course.” Then a production incident hands you a blank prompt and the playlist doesn’t play in your head.&lt;/p&gt;

&lt;p&gt;That gap—&lt;strong&gt;tutorial consumer → operator&lt;/strong&gt;—is the product problem I’m building around with CMD Master.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tutorial consumption trains
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Recognition of concepts when cued.&lt;/li&gt;
&lt;li&gt;Happy-path sequences in the author’s order.&lt;/li&gt;
&lt;li&gt;Confidence that is bound to narration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What operators need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Initiation without a narrator.&lt;/li&gt;
&lt;li&gt;State awareness (cwd, env, shell identity).&lt;/li&gt;
&lt;li&gt;Recovery from errors under mild stress.&lt;/li&gt;
&lt;li&gt;Transfer across CMD, PowerShell, and sometimes Bash contracts on one OS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are different muscles. Screenshots and lectures can support them. They can’t replace them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skills gap in concrete behaviors
&lt;/h2&gt;

&lt;p&gt;When someone is still a consumer, you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Panic on a non-zero exit they can’t name.&lt;/li&gt;
&lt;li&gt;Paste of PowerShell into CMD (or the reverse) without noticing.&lt;/li&gt;
&lt;li&gt;PATH “fixes” without verifying with &lt;code&gt;where&lt;/code&gt; / &lt;code&gt;Get-Command&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Avoidance of spaces/non-ASCII until reality forces a corruption bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When someone is becoming an operator, you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They inspect before they escalate.&lt;/li&gt;
&lt;li&gt;They change one variable at a time.&lt;/li&gt;
&lt;li&gt;They can explain &lt;em&gt;which shell&lt;/em&gt; they’re in and why the quoting looks that way.&lt;/li&gt;
&lt;li&gt;They write thin glue instead of mythic one-liners.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What CMD Master is (without launch-speak)
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Curriculum pillars mapped to the gap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consumer habit&lt;/th&gt;
&lt;th&gt;Operator replacement drill&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Watch PATH video&lt;/td&gt;
&lt;td&gt;Break PATH on purpose; repair with verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy pipe examples&lt;/td&gt;
&lt;td&gt;Pipe objects; then pipe native text; name the difference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read Execution Policy thread&lt;/td&gt;
&lt;td&gt;Reproduce block; list scopes; narrow bypass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finish playlist&lt;/td&gt;
&lt;td&gt;Cold prompt: complete a six-step mission without narration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CMD Master’s job is to make the right column cheaper to practice than the left column is to binge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What success looks like (non-vanity)
&lt;/h2&gt;

&lt;p&gt;Not DAU cosplay. Success is: a Windows-first junior who can enter an unfamiliar shell, identify it, run a safe diagnostic, and recover from a non-zero exit without waiting for a YouTube reassuring voiceover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft CTA philosophy (why one link)
&lt;/h2&gt;

&lt;p&gt;This article is showdev with a point of view, not a landing page. One contextual link is enough; the work should make the next click optional rather than begged. Profile bio can carry the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build roadmap (honest, unfinished)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Better early pacing (post–prompt #3 scar).&lt;/li&gt;
&lt;li&gt;More diagnostic missions (PATH, encoding, policy).&lt;/li&gt;
&lt;li&gt;Clearer multi-shell labeling.&lt;/li&gt;
&lt;li&gt;Covers and distribution experiments (this Dev.to Plan A queue).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operators aren’t manufactured by a single article. They’re manufactured by repeated, honest practice under mild friction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master&lt;/a&gt; is free interactive browser-terminal learning for CMD, PowerShell, and Bash, aimed at Windows-first learners. The design bet is simple: &lt;strong&gt;reps at a real prompt with feedback&lt;/strong&gt; beat passive tours.&lt;/p&gt;

&lt;p&gt;What we’re targeting in drills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early wins without lying about difficulty.&lt;/li&gt;
&lt;li&gt;Explicit shell contracts.&lt;/li&gt;
&lt;li&gt;Diagnostics (PATH, exit codes, quoting, encoding) as first-class—not appendices.&lt;/li&gt;
&lt;li&gt;Soft boundaries so prompt #3 isn’t a cliff (learned the hard way).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we refuse
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shame about CMD.&lt;/li&gt;
&lt;li&gt;“Linux first or go home” gatekeeping.&lt;/li&gt;
&lt;li&gt;Mega CTAs and hype posts pretending a trainer replaces a job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Showdev status
&lt;/h2&gt;

&lt;p&gt;This is still product work with fingerprints: quit points, myth-driven curriculum rewrites, comparison lessons for 5.1 vs 7 realities. If you teach Windows CLI, steal the sequencing ideas even if you never click the link.&lt;/p&gt;

&lt;p&gt;The gap won’t close because someone watched one more video. It closes when the blank prompt stops feeling like a test they didn’t study for—and starts feeling like a tool they know how to pressure-test.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>learning</category>
      <category>cli</category>
      <category>windows</category>
    </item>
    <item>
      <title>PowerShell Move-Item: Sort a Messy Downloads Folder by File Type in One Script</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Mon, 19 Oct 2026 07:19:55 +0000</pubDate>
      <link>https://dev.to/arnostorg/powershell-move-item-sort-a-messy-downloads-folder-by-file-type-in-one-script-3f31</link>
      <guid>https://dev.to/arnostorg/powershell-move-item-sort-a-messy-downloads-folder-by-file-type-in-one-script-3f31</guid>
      <description>&lt;p&gt;Every Downloads folder ends up the same way: 400 files, three of them named &lt;code&gt;invoice (7).pdf&lt;/code&gt;, a dozen half-unzipped archives, and screenshots from a meeting nobody remembers. Dragging them into folders by hand takes twenty minutes. One &lt;code&gt;Move-Item&lt;/code&gt; script does it in a second, and it can show you a full preview before it touches anything.&lt;/p&gt;

&lt;p&gt;This post is that script, built up in small steps, plus the three &lt;code&gt;Move-Item&lt;/code&gt; behaviours that bite people the first time they automate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-second refresher
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Move-Item&lt;/code&gt; (aliases &lt;code&gt;move&lt;/code&gt;, &lt;code&gt;mv&lt;/code&gt;, &lt;code&gt;mi&lt;/code&gt;) moves a file or folder to a new location. If the destination is an existing folder, the item lands inside it with its name unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\Downloads\report.pdf"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\Documents"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two switches make it safe to automate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Switch&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-WhatIf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prints what would move and moves nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-PassThru&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outputs the moved item at its new location, so you can log it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full parameter list is in the &lt;a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/move-item" rel="noopener noreferrer"&gt;official Move-Item documentation on Microsoft Learn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: see what you are dealing with
&lt;/h2&gt;

&lt;p&gt;Before sorting anything, group the files by extension so you know which folders you will get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\Downloads"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Group-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Count Name
----- ----
  142 .pdf
   61 .png
   38 .zip
   17 .xlsx
    9 .msi
    3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last row with no name is files that have no extension at all. The script needs to handle those too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: the sort script, in dry-run mode
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;\Downloads"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$dryRun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# flip to $false when the preview looks right&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Extension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'no-extension'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ext&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$dryRun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$dryRun&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it as is and you get one &lt;code&gt;What if:&lt;/code&gt; line per file and nothing changes on disk. Read the list. When it looks right, set &lt;code&gt;$dryRun = $false&lt;/code&gt; and run it again.&lt;/p&gt;

&lt;p&gt;A few choices in there are deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$files = Get-ChildItem ...&lt;/code&gt; first, then loop.&lt;/strong&gt; Collecting the list up front means the script works from a fixed snapshot instead of reading the folder while it is changing underneath it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-WhatIf:$dryRun&lt;/code&gt; on &lt;code&gt;New-Item&lt;/code&gt; too.&lt;/strong&gt; If you only put &lt;code&gt;-WhatIf&lt;/code&gt; on &lt;code&gt;Move-Item&lt;/code&gt;, the dry run still creates a pile of empty &lt;code&gt;pdf&lt;/code&gt;, &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;zip&lt;/code&gt; folders. Passing the same switch to both keeps the preview truly side-effect free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.ToLower()&lt;/code&gt;.&lt;/strong&gt; Otherwise &lt;code&gt;.PDF&lt;/code&gt; and &lt;code&gt;.pdf&lt;/code&gt; would compete for the same folder name. Windows treats them as one folder, but the output looks messy and confusing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-LiteralPath&lt;/code&gt;, not &lt;code&gt;-Path&lt;/code&gt;.&lt;/strong&gt; More on that in the gotchas below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the helpers explained in depth, I covered &lt;a href="https://dev.to/arnostorg/powershell-join-path-build-file-paths-without-slash-bugs-3jnb"&gt;building folder paths with Join-Path&lt;/a&gt; and &lt;a href="https://dev.to/arnostorg/powershell-test-path-check-a-path-exists-before-you-act-3fn3"&gt;checking a path exists with Test-Path&lt;/a&gt; in earlier tips.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: do not lose &lt;code&gt;invoice.pdf&lt;/code&gt; number two
&lt;/h2&gt;

&lt;p&gt;Here is the first real-world failure. If &lt;code&gt;pdf\invoice.pdf&lt;/code&gt; already exists and you move another &lt;code&gt;invoice.pdf&lt;/code&gt; into the same folder, &lt;code&gt;Move-Item&lt;/code&gt; stops with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Move-Item: Cannot create a file when that file already exists.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-Force&lt;/code&gt; makes the error go away by &lt;strong&gt;overwriting&lt;/strong&gt; the existing file, which is almost never what you want in a Downloads cleanup. Instead, pick a free name the way File Explorer does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{0} ({1}){2}'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BaseName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Extension&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$dryRun&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap that block in for the single &lt;code&gt;Move-Item&lt;/code&gt; line from Step 2. Now the second invoice becomes &lt;code&gt;invoice (1).pdf&lt;/code&gt; and nothing gets silently replaced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: keep a log of what moved
&lt;/h2&gt;

&lt;p&gt;When someone asks "where did my installer go?", you want an answer. &lt;code&gt;-PassThru&lt;/code&gt; returns the item at its new location, so you can write it straight to a log file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PassThru&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'{0}  {1}  -&amp;gt;  {2}'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Add-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;\_moves.log"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this only after the dry run, because nothing is passed through while &lt;code&gt;-WhatIf&lt;/code&gt; is on. The log gives you a timestamped line for every move, which is also everything you need to undo the cleanup by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three Move-Item gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A missing destination folder turns your move into a rename.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# C:\Reports\Archive does NOT exist yet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports\Archive&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first file is moved and renamed to a file called &lt;code&gt;Archive&lt;/code&gt; with no extension. The second file then fails, because &lt;code&gt;Archive&lt;/code&gt; now exists as a file. You end up with one mystery file and a screen of red errors. Always create the destination folder first, which is exactly why Step 2 runs &lt;code&gt;Test-Path&lt;/code&gt; and &lt;code&gt;New-Item&lt;/code&gt; before moving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Square brackets in file names break &lt;code&gt;-Path&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-Path&lt;/code&gt; treats &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; as wildcard characters, so this finds nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Temp\report [final].pdf"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Archive&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downloads are full of names like &lt;code&gt;photo [1].jpg&lt;/code&gt;. Use &lt;code&gt;-LiteralPath&lt;/code&gt; whenever the name comes from real files rather than a pattern you typed. Piping &lt;code&gt;Get-ChildItem&lt;/code&gt; output into &lt;code&gt;Move-Item&lt;/code&gt; is safe as well, because the pipeline binds the exact path for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Moving folders to another drive depends on your PowerShell version.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving a &lt;em&gt;file&lt;/em&gt; from &lt;code&gt;C:&lt;/code&gt; to &lt;code&gt;D:&lt;/code&gt; works everywhere. Moving a &lt;em&gt;folder&lt;/em&gt; across drives in Windows PowerShell 5.1 fails with &lt;code&gt;Source and destination path must have identical roots&lt;/code&gt;. Current PowerShell 7 releases handle it by copying and then deleting the source. On a locked work laptop stuck on 5.1, use &lt;code&gt;Copy-Item -Recurse&lt;/code&gt;, check the copy, and only then remove the original (with a dry run first, as in the &lt;a href="https://dev.to/arnostorg/powershell-remove-item-dry-run-deletes-with-whatif-before-you-nuke-folders-14ec"&gt;Remove-Item -WhatIf tip&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: move by age instead of type
&lt;/h2&gt;

&lt;p&gt;The same pattern archives old files from a reports folder. This one moves anything not touched in 30 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\Reports\Archive'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$cutoff&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LiteralPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Reports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-lt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cutoff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Move-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$archive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the folder gets created before the pipeline runs, so gotcha number one never gets a chance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice the moves before you run them on real files
&lt;/h2&gt;

&lt;p&gt;Typing these out on your actual Downloads folder for the first time is a bit nerve-wracking. In &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master's free interactive PowerShell lessons&lt;/a&gt; you can practise &lt;code&gt;Move-Item&lt;/code&gt;, &lt;code&gt;-WhatIf&lt;/code&gt;, and folder creation in a live browser terminal with instant feedback, with no install and nothing real to lose. The same &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;hands-on Windows command line course&lt;/a&gt; covers CMD and Bash too, if you switch between shells at work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your turn:&lt;/strong&gt; what does your Downloads folder look like right now? Run the Step 1 command and share your top extension in the comments. I am betting on &lt;code&gt;.pdf&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>scripting</category>
      <category>windows</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Three Windows CLI Myths I Believed Until I Taught Them</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Sun, 18 Oct 2026 13:25:41 +0000</pubDate>
      <link>https://dev.to/arnostorg/three-windows-cli-myths-i-believed-until-i-taught-them-3ljd</link>
      <guid>https://dev.to/arnostorg/three-windows-cli-myths-i-believed-until-i-taught-them-3ljd</guid>
      <description>&lt;p&gt;Teaching is a myth detection machine. Ideas that feel true when you’re solo break when twenty learners follow them literally. Here are three Windows CLI myths I believed—and what replaced them after I tried teaching them out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 1: “CMD is obsolete; skip it.”
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I believed:&lt;/strong&gt; PowerShell is the curriculum. CMD is history trivia.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What teaching revealed:&lt;/strong&gt; Learners hit vendor &lt;code&gt;.bat&lt;/code&gt; files, constrained images, and support scripts in the first month. Skipping CMD didn’t make them modern. It made them brittle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement:&lt;/strong&gt; Teach CMD as a &lt;strong&gt;constrained toolkit&lt;/strong&gt; for file ops and legacy glue, then graduate to PowerShell for structured automation. No shame lane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 2: “If they learn Bash on Linux/WSL first, Windows will be easy.”
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I believed:&lt;/strong&gt; Transfer will Just Happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What teaching revealed:&lt;/strong&gt; Quoting, path shapes, and pipeline semantics don’t transfer automatically. Learners who only practiced Bash treated PowerShell as broken Bash and blamed themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement:&lt;/strong&gt; Platform-honest practice on the shell they’ll use this week. Add Git Bash/WSL as an explicit second contract—not as the “real” one. (Related rant energy, calmer delivery.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Myth 3: “Tab completion + copy-paste is enough to become dangerous-in-a-good-way.”
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I believed:&lt;/strong&gt; Ergonomics unlock competence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What teaching revealed:&lt;/strong&gt; Learners could assemble commands they couldn’t debug. Exit codes, encoding, and Execution Policy inspection were missing. They looked fluent until the first red error without a screenshot twin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replacement:&lt;/strong&gt; Every module needs recovery reps. Completion is allowed; semantics are required.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I “tried” these myths
&lt;/h2&gt;

&lt;p&gt;Not in a lab coat—in cohorts and office hours. I taught the myth version, watched the failure mode, then changed the lesson. The bruise patterns repeated enough to retire the myths.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do differently in materials now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Name the shell on every example.&lt;/li&gt;
&lt;li&gt;Put non-ASCII and spaces in paths on purpose.&lt;/li&gt;
&lt;li&gt;Force &lt;code&gt;where&lt;/code&gt; / &lt;code&gt;Get-Command&lt;/code&gt; and exit code checks early.&lt;/li&gt;
&lt;li&gt;Keep soft links to practice environments instead of launch-blog energy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the myths showed up in office hours
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;“I skipped CMD and now this vendor script is hieroglyphs.”&lt;/li&gt;
&lt;li&gt;“I practiced on Ubuntu; PowerShell quoting feels random.”&lt;/li&gt;
&lt;li&gt;“I tab-completed everything; why did CI fail with exit 1?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each sentence is a curriculum smell. If your materials produce those office-hour patterns, update the materials—don’t blame the learner’s grit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacement myths I refuse to install
&lt;/h2&gt;

&lt;p&gt;I also won’t install the reverse dogma (“never learn Linux,” “PowerShell is always wrong,” “GUIs are cheating”). Operator culture should be greedy for tools and precise about contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invite for fellow instructors
&lt;/h2&gt;

&lt;p&gt;If you’ve retired a CLI myth after teaching it, keep a changelog. Curriculum honesty compounds. Myths hate changelogs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth almost-myth I nearly included
&lt;/h2&gt;

&lt;p&gt;“Windows Terminal settings are the same thing as shell knowledge.” They’re not. Host configuration helps; it doesn’t teach exit codes. I cut it for length—but if you’re building a course, don’t let dockets of JSON themes replace fundamentals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing challenge
&lt;/h2&gt;

&lt;p&gt;Pick one myth you still teach “because everyone says it.” Teach the opposite for one cohort week—with measurement. Keep whichever version produces colder-prompt competence. Loyalty to slogans is optional; loyalty to outcomes isn’t.&lt;/p&gt;

&lt;p&gt;If you want a place that assumes Windows-first operators deserve serious drills, I’m building that practice surface as &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master&lt;/a&gt;—free interactive browser-terminal learning for CMD, PowerShell, and Bash.&lt;/p&gt;

&lt;p&gt;Myths feel efficient. Teaching makes them expensive. Pay the cost once in curriculum design, or pay it forever in office hours.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>cli</category>
      <category>teaching</category>
      <category>learning</category>
    </item>
    <item>
      <title>Oh My Posh Looks Cool. Here’s When It’s Worth the Setup Cost</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Sat, 17 Oct 2026 13:28:53 +0000</pubDate>
      <link>https://dev.to/arnostorg/oh-my-posh-looks-cool-heres-when-its-worth-the-setup-cost-4cbd</link>
      <guid>https://dev.to/arnostorg/oh-my-posh-looks-cool-heres-when-its-worth-the-setup-cost-4cbd</guid>
      <description>&lt;p&gt;Oh My Posh makes a Windows prompt look like a conference talk. Glyphs, segments, Git status, cloud context—eye candy with a purpose. I ran it, loved it, over-invested, then ripped most of it out. This retrospective is about &lt;strong&gt;when the setup cost is worth it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The real costs (not the install one-liner)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Font dependency.&lt;/strong&gt; Nerd Fonts (or equivalent) across terminals, VS Code, remote sessions. Miss one host and your prompt becomes mojibake theater.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Startup tax.&lt;/strong&gt; Heavy segments that shell out to git/network on every prompt add friction on slow disks and large repos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive load for demos.&lt;/strong&gt; Students stare at your diamonds instead of your command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability.&lt;/strong&gt; Your brain expects segments that aren’t on locked corporate shells.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Install is easy. Habitat change isn’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it &lt;em&gt;was&lt;/em&gt; worth it for me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Daily driver machine I control end-to-end.&lt;/li&gt;
&lt;li&gt;Work where &lt;strong&gt;repo/branch awareness&lt;/strong&gt; prevents wrong-directory deploys.&lt;/li&gt;
&lt;li&gt;A prompt theme with &lt;strong&gt;few segments&lt;/strong&gt; (path + git + error) rather than a Christmas tree.&lt;/li&gt;
&lt;li&gt;After the shell fundamentals were already boring and solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. Prompt bling on top of confused exit codes is cosplay.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it wasn’t
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Teaching sessions (I switched to a plain prompt).&lt;/li&gt;
&lt;li&gt;Jump boxes and customer machines.&lt;/li&gt;
&lt;li&gt;CI-like terminals.&lt;/li&gt;
&lt;li&gt;Any machine where installing fonts was a ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also: when I realized I was tuning theme JSON instead of finishing the task that made me open the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pragmatic middle path
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use Oh My Posh (or a minimal PS prompt function) on &lt;strong&gt;personal/dev&lt;/strong&gt; profiles only.&lt;/li&gt;
&lt;li&gt;Keep a &lt;code&gt;-NoProfile&lt;/code&gt; / plain profile muscle for teaching and support.&lt;/li&gt;
&lt;li&gt;Cap segments ruthlessly. If a segment doesn’t prevent mistakes, it’s decoration.&lt;/li&gt;
&lt;li&gt;Measure prompt time on your heaviest repo. If you feel it, cut it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Retrospective verdict
&lt;/h2&gt;

&lt;p&gt;Oh My Posh is worth the setup cost when it &lt;strong&gt;reduces wrong-context errors&lt;/strong&gt; and you control the surface area. It’s not worth it as identity, as a teaching default, or as a substitute for learning the shell underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  My “worth it” segment budget
&lt;/h2&gt;

&lt;p&gt;Allowed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;current path (shortened),&lt;/li&gt;
&lt;li&gt;git branch + dirty state,&lt;/li&gt;
&lt;li&gt;last command success/fail marker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Denied (for me):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;weather,&lt;/li&gt;
&lt;li&gt;Kubernetes context on every prompt when I’m not on-call for it,&lt;/li&gt;
&lt;li&gt;multiple cloud account glyphs,&lt;/li&gt;
&lt;li&gt;clocks I can see on the taskbar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your budget may differ. The point is having a budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollback plan
&lt;/h2&gt;

&lt;p&gt;Keep a &lt;code&gt;prompt_plain&lt;/code&gt; profile (or &lt;code&gt;-NoProfile&lt;/code&gt; habit) you can switch to in one click. If your bling can’t be disabled during an incident, it’s an operational liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching tax, restated
&lt;/h2&gt;

&lt;p&gt;Every glyph you add is something a learner might think is required knowledge. If it isn’t, hide it when you teach. Cool prompts are for machines you own; classrooms need the signal-to-noise ratio of a runway, not a festival.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ninety-day keep/kill (prompt edition)
&lt;/h2&gt;

&lt;p&gt;Kept: path + git + error marker on personal pwsh.&lt;br&gt;&lt;br&gt;
Killed: network segments, excessive glyphs, identical theme forced onto teaching demos.&lt;br&gt;&lt;br&gt;
Maybe later: a second theme for “focus mode” with path only.&lt;/p&gt;

&lt;p&gt;The retrospective isn’t anti-beauty. It’s pro-mistake-reduction accounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Font failure mode (so you’re not surprised)
&lt;/h2&gt;

&lt;p&gt;Remote desktop to a box without Nerd Fonts and your prompt becomes tofu boxes. If that box is a customer jump host, you just spent cognitive budget on cosmetics during an incident. Keep a plain profile reachable in one switch.&lt;/p&gt;

&lt;p&gt;I’m biased toward plain prompts in learning products for that reason—see the deliberately un-fancy practice UX in &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master&lt;/a&gt;. Make the skill loud. Keep the chrome quiet until it earns a slot.&lt;/p&gt;

&lt;p&gt;Cool is allowed. Cost accounting is required.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>windows</category>
      <category>terminal</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Should Corporates Ban Personal PowerShell Profiles?</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:23:56 +0000</pubDate>
      <link>https://dev.to/arnostorg/should-corporates-ban-personal-powershell-profiles-1ki8</link>
      <guid>https://dev.to/arnostorg/should-corporates-ban-personal-powershell-profiles-1ki8</guid>
      <description>&lt;h1&gt;
  
  
  Should Corporates Ban Personal PowerShell Profiles?
&lt;/h1&gt;

&lt;p&gt;Personal PowerShell profiles are cozy. They also scare security teams: persistent custom code on jump boxes, shadowing built-ins, sneaky aliases that change what a runbook means, credentials that “temporarily” landed in a profile in 2019 and never left.&lt;/p&gt;

&lt;p&gt;So—&lt;strong&gt;should corporates ban them?&lt;/strong&gt; I don’t think the binary question is the useful one. The useful one is: what risk are you actually managing, and what operator freedom are you willing to trade?&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman for bans / lockdowns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared admin hosts&lt;/strong&gt; shouldn’t depend on per-user surprise behavior.&lt;/li&gt;
&lt;li&gt;Profiles can hide &lt;strong&gt;malware-ish persistence&lt;/strong&gt; among legitimate UX.&lt;/li&gt;
&lt;li&gt;Audit and change control get harder when every shell is unique.&lt;/li&gt;
&lt;li&gt;Junior admins copy internet profiles that override &lt;code&gt;curl&lt;/code&gt;, truncate history, or phone home for prompt themes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your environment is high-assurance and shared, “no arbitrary profile code” is a defensible default.&lt;/p&gt;

&lt;h2&gt;
  
  
  The steelman for allowing constrained profiles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Experts are faster with carefully chosen functions.&lt;/li&gt;
&lt;li&gt;Absolute bans push work into &lt;strong&gt;shadow workarounds&lt;/strong&gt; (USB scripts, personal laptops, unexplained binaries).&lt;/li&gt;
&lt;li&gt;Teaching and onboarding sometimes &lt;em&gt;need&lt;/em&gt; a controlled profile to demonstrate good patterns.&lt;/li&gt;
&lt;li&gt;Not all hosts are equal: a developer laptop ≠ a tier-0 jump box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A blanket ban across every Windows box treats unequal risk as equal.&lt;/p&gt;

&lt;h2&gt;
  
  
  A third option: policy by tier
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Profile stance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tier-0 / break-glass&lt;/td&gt;
&lt;td&gt;Locked: signed profiles only or none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared ops jump boxes&lt;/td&gt;
&lt;td&gt;Controlled: company-blessed profile repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer endpoints&lt;/td&gt;
&lt;td&gt;Allowed: guidance + secret scanning education&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training sandboxes&lt;/td&gt;
&lt;td&gt;Encouraged: teach clean vs dirty profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is more work than a meme policy. It’s also more aligned with how incidents actually happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Norms that beat pure prohibition
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No secrets in profiles. Ever. Use secret stores.&lt;/li&gt;
&lt;li&gt;Don’t shadow critical commands without extreme cause.&lt;/li&gt;
&lt;li&gt;Prefer dot-sourced company modules over 500-line personal novels.&lt;/li&gt;
&lt;li&gt;Demonstrate scripts with &lt;code&gt;pwsh -NoProfile&lt;/code&gt; when reproducibility matters.&lt;/li&gt;
&lt;li&gt;Code review the blessed profile the same way you review other tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I’m asking the community
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Failure stories I’ve heard (composites)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An alias redefining a common command caused a runbook to skip a step on one admin’s laptop only.&lt;/li&gt;
&lt;li&gt;A “temporary” credential in a profile synced via a dotfiles repo.&lt;/li&gt;
&lt;li&gt;A prompt theme calling network APIs slowed every shell on a restricted VLAN until it looked like an outage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these require assuming malice. They require assuming profiles are code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A question for security and platform teams
&lt;/h2&gt;

&lt;p&gt;If you ban profiles but don’t provide a blessed, reviewed module of shared helpers, you’re not removing customization—you’re pushing it underground. Pair controls with an official path for ergonomics.&lt;/p&gt;

&lt;h2&gt;
  
  
  My personal vote (not a policy template)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ban or lock on tier-0.&lt;/li&gt;
&lt;li&gt;Curate on shared ops hosts.&lt;/li&gt;
&lt;li&gt;Guide and educate on developer laptops.&lt;/li&gt;
&lt;li&gt;Always provide a blessed alternative for common ergonomics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you only ban, you outsource UX to shadow IT. If you only allow, you outsource risk to hope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discuss framing for mixed audiences
&lt;/h2&gt;

&lt;p&gt;Security folks hear “ban.” Developers hear “trust me.” Platform folks hear “tiered control plane.” If your org can only have one conversation, have the tiered one—it forces everyone to name which machines they’re actually talking about.&lt;/p&gt;

&lt;p&gt;I’m not selling a product feature here so much as an ops culture question—though I’ll admit clean-shell teaching is part of why I care about profile hygiene when building lessons for &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discuss:&lt;/strong&gt; In your org, are personal PowerShell profiles banned, ignored, or curated—and which failure mode have you actually seen: malware persistence, runbook confusion, or death-by-tooling-rigidity?&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>security</category>
      <category>enterprise</category>
      <category>discuss</category>
    </item>
    <item>
      <title>PowerShell Remove-Item: Dry-Run Deletes with -WhatIf Before You Nuke Folders</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Fri, 16 Oct 2026 07:24:16 +0000</pubDate>
      <link>https://dev.to/arnostorg/powershell-remove-item-dry-run-deletes-with-whatif-before-you-nuke-folders-14ec</link>
      <guid>https://dev.to/arnostorg/powershell-remove-item-dry-run-deletes-with-whatif-before-you-nuke-folders-14ec</guid>
      <description>&lt;p&gt;You are about to wipe a folder on a locked work laptop. Recycle Bin is policy-disabled. There is no "oops, restore from backup" button for the next ten minutes. &lt;code&gt;Remove-Item&lt;/code&gt; will do exactly what you typed — including the path you thought you typed. The only free safety net is a dry run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mental model: preview, then destroy
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Remove-Item&lt;/code&gt; (aliases: &lt;code&gt;rm&lt;/code&gt;, &lt;code&gt;del&lt;/code&gt;, &lt;code&gt;ri&lt;/code&gt;) deletes files and folders. Two common parameters change &lt;em&gt;how sure&lt;/em&gt; you are before anything is gone:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Switch&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-WhatIf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prints what &lt;em&gt;would&lt;/em&gt; be deleted. Touches nothing.&lt;/td&gt;
&lt;td&gt;Always before a recursive or wildcard delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Confirm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Asks Yes/No/All for each target (or a batch prompt)&lt;/td&gt;
&lt;td&gt;Interactive cleanup when you want a human gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Recurse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Walks into folders&lt;/td&gt;
&lt;td&gt;Folder trees — and the place most people regret speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-Force&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overrides read-only / hidden / some prompts&lt;/td&gt;
&lt;td&gt;Only after &lt;code&gt;-WhatIf&lt;/code&gt; looked correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Windows PowerShell 5.1 and PowerShell 7 the switches work the same for this workflow. The difference that matters is you, not the version: dry-run first, then remove the safety switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 1: dry-run with -WhatIf
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;tmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get lines like &lt;code&gt;What if: Performing the operation "Remove File" on target "C:\Logs\old.tmp".&lt;/code&gt; Read every path. If one of them is &lt;code&gt;C:\Logs&lt;/code&gt; itself or a sibling you did not mean, stop. Fix the pattern. Run &lt;code&gt;-WhatIf&lt;/code&gt; again until the list is boring and correct.&lt;/p&gt;

&lt;p&gt;Wildcard preview for a whole tree of junk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Temp\BuildCache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Extension&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.tmp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'.log'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'.bak'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Piping into &lt;code&gt;Remove-Item -WhatIf&lt;/code&gt; still only &lt;em&gt;simulates&lt;/em&gt;. Nothing is deleted until you drop &lt;code&gt;-WhatIf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 2: -Confirm when you want a human gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Staging\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Confirm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell prompts before each item (or offers Yes to All). Use this when the set is small and you are watching the screen. Do &lt;strong&gt;not&lt;/strong&gt; rely on &lt;code&gt;-Confirm&lt;/code&gt; inside a scheduled task or a script that runs unattended — there is nobody to answer, and depending on host settings the call can hang or fail.&lt;/p&gt;

&lt;p&gt;For scripts, prefer the &lt;code&gt;-WhatIf&lt;/code&gt; review in a terminal, then a second run without it. For interactive one-offs at a desk, &lt;code&gt;-Confirm&lt;/code&gt; is fine.&lt;/p&gt;

&lt;p&gt;Combine both when you are nervous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Staging\export-2026-10-15&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Confirm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-WhatIf&lt;/code&gt; wins: you still get a preview and no delete. Drop &lt;code&gt;-WhatIf&lt;/code&gt; when the preview is approved; keep &lt;code&gt;-Confirm&lt;/code&gt; if you still want click-through safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 3: delete a folder tree carefully
&lt;/h2&gt;

&lt;p&gt;A non-empty folder refuses a bare &lt;code&gt;Remove-Item&lt;/code&gt; without &lt;code&gt;-Recurse&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fails if C:\Logs\OldRuns still has files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\OldRuns&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The careful pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Preview the tree&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\OldRuns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# 2) Dry-run the delete&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\OldRuns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# 3) Only then&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs\OldRuns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On locked corporate images, deleted items often skip the Recycle Bin entirely when removed from PowerShell. Treat step 2 as mandatory, not optional theater.&lt;/p&gt;

&lt;p&gt;PowerShell 7 tip: same switches. If you delete large trees often, preview with &lt;code&gt;Get-ChildItem&lt;/code&gt; first so you see count and depth before &lt;code&gt;Remove-Item&lt;/code&gt; walks them again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 4: the footgun — wrong path, wildcards, and -Force
&lt;/h2&gt;

&lt;p&gt;These are the tickets that start with "I only meant to clear temp files."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong path / too-broad wildcard&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Looks local. If you are in C:\ and mistype, you can match far more than you think.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always prefer an absolute path for destructive work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Apps\MyService\cache\&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;-Force&lt;/code&gt; hides warnings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-Force&lt;/code&gt; clears the read-only bit and pushes through some confirmation paths. It does &lt;strong&gt;not&lt;/strong&gt; make a bad path safe. Combining &lt;code&gt;-Recurse -Force&lt;/code&gt; without a prior &lt;code&gt;-WhatIf&lt;/code&gt; is how a typo in &lt;code&gt;C:\Logs&lt;/code&gt; becomes &lt;code&gt;C:\Log&lt;/code&gt; or worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hidden / system files&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'*.tmp'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WhatIf&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;-Force&lt;/code&gt; on &lt;code&gt;Get-ChildItem&lt;/code&gt; to &lt;em&gt;see&lt;/em&gt; hidden targets in the preview. Use &lt;code&gt;-Force&lt;/code&gt; on &lt;code&gt;Remove-Item&lt;/code&gt; only after that list looks right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Providers other than the filesystem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Remove-Item&lt;/code&gt; also works on registry keys, certificates, and other providers. A path that starts with &lt;code&gt;HKLM:\&lt;/code&gt; or &lt;code&gt;Cert:\&lt;/code&gt; is not a folder on disk. If your muscle memory is filesystem-only, pause when the provider prefix is not &lt;code&gt;C:\&lt;/code&gt; / &lt;code&gt;\\server\share&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe default ritual
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write the command with a full path.&lt;/li&gt;
&lt;li&gt;Append &lt;code&gt;-WhatIf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the preview like a change ticket.&lt;/li&gt;
&lt;li&gt;Re-run the same line without &lt;code&gt;-WhatIf&lt;/code&gt; (add &lt;code&gt;-Confirm&lt;/code&gt; if you still want prompts).&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;-Force&lt;/code&gt; off until a read-only/hidden block is a known, reviewed exception.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That ritual is slower than a one-liner. It is also how you keep your job and the team's night quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice deletes somewhere that cannot hurt production
&lt;/h2&gt;

&lt;p&gt;The scary part of &lt;code&gt;Remove-Item&lt;/code&gt; is that real machines forgive typos slowly. In &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master's interactive PowerShell lessons&lt;/a&gt; you can run &lt;code&gt;Remove-Item&lt;/code&gt;, &lt;code&gt;-WhatIf&lt;/code&gt;, and &lt;code&gt;-Confirm&lt;/code&gt; in a live browser terminal with instant feedback — no install, no VM, no Recycle Bin policy surprises. The same &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;free Windows command line practice site&lt;/a&gt; covers CMD and Bash when you hop between shells.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your turn:&lt;/strong&gt; what is the worst almost-delete &lt;code&gt;-WhatIf&lt;/code&gt; ever saved you from? Drop the story (sanitized paths welcome) in the comments.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>scripting</category>
      <category>windows</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Tab Completion Won’t Save You From Not Understanding Exit Codes</title>
      <dc:creator>arnostorg</dc:creator>
      <pubDate>Thu, 15 Oct 2026 13:38:01 +0000</pubDate>
      <link>https://dev.to/arnostorg/tab-completion-wont-save-you-from-not-understanding-exit-codes-4an6</link>
      <guid>https://dev.to/arnostorg/tab-completion-wont-save-you-from-not-understanding-exit-codes-4an6</guid>
      <description>&lt;p&gt;I tried an experiment on myself: lean hard on tab completion and IntelliSense-ish shell helpers for two weeks, and deliberately &lt;em&gt;not&lt;/em&gt; study exit codes beyond vibes. I wanted to see how far ergonomics carry you.&lt;/p&gt;

&lt;p&gt;Far enough to look productive. Not far enough to be safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  The seductive part
&lt;/h2&gt;

&lt;p&gt;Tab completion reduces spelling errors. It surfaces parameters. It makes PowerShell feel modern. Students love it. I love it. This post is not anti-completion.&lt;/p&gt;

&lt;p&gt;It’s anti-&lt;strong&gt;substituting UX for semantics&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I got burned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pipelines that “looked fine”
&lt;/h3&gt;

&lt;p&gt;A native tool failed halfway. The screen still printed something. Because I didn’t check &lt;code&gt;$LASTEXITCODE&lt;/code&gt; / &lt;code&gt;%ERRORLEVEL%&lt;/code&gt;, a later step ran on partial output. Tab completion had helped me type the broken pipeline faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI vs laptop gap
&lt;/h3&gt;

&lt;p&gt;Locally, aliases and profile helpers padded mistakes. In CI, bare shells told the truth. Completion wasn’t available in the same way; exit codes suddenly mattered and I had no muscle memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  False green in batch wrappers
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;.bat&lt;/code&gt; called a tool, ignored failure, exited 0 by accident of flow. The wrapper “completed.” Completion never enters that story. Semantics do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tiny model that actually saves you
&lt;/h2&gt;

&lt;p&gt;Processes signal outcome with an &lt;strong&gt;exit code&lt;/strong&gt;. By convention, &lt;code&gt;0&lt;/code&gt; means success; non-zero means failure (with program-specific meanings).&lt;/p&gt;

&lt;p&gt;PowerShell complications worth learning early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$LASTEXITCODE&lt;/code&gt; for native executables.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-not $?&lt;/code&gt; / &lt;code&gt;$true&lt;/code&gt; &lt;code&gt;$false&lt;/code&gt; patterns for shell-level success.&lt;/li&gt;
&lt;li&gt;Cmdlet errors vs native exit codes—different channels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CMD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;mytool&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;%ERRORLEVEL%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mytool.exe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$LASTEXITCODE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put that reflex before you chase prettier prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The i_tried rule I kept afterward
&lt;/h2&gt;

&lt;p&gt;For every new tool, I practice one happy run &lt;strong&gt;and&lt;/strong&gt; one forced failure, then read the code. Tab-complete the flags. Still read the code.&lt;/p&gt;

&lt;p&gt;When teaching, I now introduce completion as a speed tool &lt;em&gt;after&lt;/em&gt; a first intentional failure exercise. Otherwise learners optimize for “command accepted by the completer,” not “operation succeeded.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Product note
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Two-week experiment protocol (replicable)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Week A: completion on, exit-code checks off (honor system).&lt;/li&gt;
&lt;li&gt;Week B: completion still on, but every native call followed by an explicit status check in a learning log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My log’s Week A was full of “looked fine.” Week B caught three silent partial failures I’d have pasted into a ticket as “weird output.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching script you can steal
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run a command that succeeds; show &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run a command that fails; show non-zero.&lt;/li&gt;
&lt;li&gt;Chain with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; / &lt;code&gt;||&lt;/code&gt; (CMD) or PowerShell equivalents carefully.&lt;/li&gt;
&lt;li&gt;Only then celebrate tab completion as speed—not as truth.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  PowerShell-specific footgun
&lt;/h2&gt;

&lt;p&gt;Cmdlet errors and native exit codes aren’t the same channel. A pipeline can look chatty while &lt;code&gt;$LASTEXITCODE&lt;/code&gt; still says the native tool failed. Completion won’t narrate that. Your checklist must.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incident postmortem one-liner
&lt;/h2&gt;

&lt;p&gt;“The command looked correct and the completer accepted it” is not a root cause. “We didn’t check the exit code before the next step” often is. Write that on a card. Completion stays; accountability stays harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pairing exercise (five minutes)
&lt;/h2&gt;

&lt;p&gt;Driver types a failing command on purpose. Navigator must state the exit code before suggesting a fix. No googling until the code is spoken aloud. It feels pedantic. It builds the reflex completion can’t.&lt;/p&gt;

&lt;p&gt;Speed without a status check is just faster fiction.&lt;/p&gt;

&lt;p&gt;Interactive drills are perfect for this: hide the next step behind a checked exit code. That’s a pattern I use while refining &lt;a href="https://windows-cli.arnost.org/en" rel="noopener noreferrer"&gt;CMD Master&lt;/a&gt;—completion optional, outcome mandatory.&lt;/p&gt;

&lt;p&gt;Ergonomics help you type. Exit codes tell you whether you should have.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>powershell</category>
      <category>windows</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
