Every recent Windows computer includes PowerShell, a shell designed for administration but very handy for small office maintenance jobs too: clearing old files out of the downloads folder, archiving last year's invoices, gathering all the PDFs scattered across a tree of subfolders. It needs no installation and, unlike the old command prompt, it works with objects rather than plain text.
The key concept: the object pipeline
When you list files with Get-ChildItem, PowerShell does not return lines of text but objects with properties: Name, Extension, Length (size in bytes), LastWriteTime and so on. These objects travel through the pipeline, the | symbol, to the next command, which can filter them or act on them.
Get-ChildItem C:\Work\Downloads -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-60) } |
Select-Object Name, Length, LastWriteTime
This line lists files older than 60 days with name, size and date. $_ stands for the current object in the pipeline. Comparison operators are written as words: -lt (less than), -gt (greater than), -eq (equal), -like (wildcard match).
The safety net: -WhatIf
Before moving or deleting anything, commands that change the system accept the -WhatIf parameter. With it, the command describes what it would do without doing it.
Get-ChildItem C:\Work\Downloads -File -Filter *.tmp |
Remove-Item -WhatIf
For each file, the output shows a line starting with "What if:" that describes the planned operation. Only after checking the list do you remove the parameter. It is the same philosophy as the dry run in Python scripts, and a habit never to drop. Alternatively, -Confirm asks for confirmation file by file.
Example 1: sorting files by extension
A folder where everything ends up can be tidied by creating a subfolder for each file type:
$source = "C:\Work\To sort"
Get-ChildItem $source -File | ForEach-Object {
$type = $_.Extension.TrimStart(".").ToLower()
if (-not $type) { $type = "no_extension" }
$destination = Join-Path $source $type
New-Item -ItemType Directory -Path $destination -Force | Out-Null
Move-Item $_.FullName -Destination $destination -WhatIf
}
New-Item -Force creates the folder only if it doesn't exist, without raising errors. Join-Path builds paths correctly, so you don't forget or duplicate the backslash.
Example 2: archiving by year and month
Old invoices can be grouped into folders such as 2024-01, 2024-02 based on their modification date:
Get-ChildItem C:\Archive\Invoices -File -Filter *.pdf |
Group-Object { $_.LastWriteTime.ToString("yyyy-MM") } |
ForEach-Object {
$folder = "C:\Archive\Invoices\$($_.Name)"
New-Item -ItemType Directory -Path $folder -Force | Out-Null
$_.Group | Move-Item -Destination $folder -WhatIf
}
Group-Object collects files sharing the same key; each group has a Name (the key, that is, year and month) and a Group (the files). The yyyy-MM format keeps alphabetical order consistent with chronological order.
Example 3: compressing an archive
To send or store a set of files, Compress-Archive creates a ZIP file:
Compress-Archive -Path C:\Archive\Invoices\2024-* -DestinationPath C:\Backup\invoices_2024.zip
The opposite command is Expand-Archive. For very large archives there are more efficient tools, but for everyday office use this is enough.
Finding the bulkiest files
When the shared drive is full, the first question is: what is taking up the space?
Get-ChildItem \\server\shared -File -Recurse -ErrorAction SilentlyContinue |
Sort-Object Length -Descending |
Select-Object -First 20 FullName, @{n="MB"; e={[math]::Round($_.Length / 1MB, 1)}}
The calculated property @{n=...; e=...} adds a column with the size in megabytes, far more readable than bytes. -ErrorAction SilentlyContinue stops folders you cannot read from filling the screen with errors.
Saving and running a script
Commands can be saved in a .ps1 file. For security reasons, however, Windows may prevent it from running. You check the execution policy with Get-ExecutionPolicy; to allow local scripts for the current user only, use:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
With RemoteSigned, scripts written on your own computer run, while those downloaded from the Internet must be signed. In companies this setting may be managed centrally by the administrator. In my work I prefer to save every script with an opening comment explaining its purpose, the folders involved and the date of the last change.
| Command | Function |
|---|---|
Get-ChildItem |
Lists files and folders |
Where-Object |
Filters by condition |
Move-Item / Copy-Item |
Moves or copies |
Remove-Item |
Deletes |
Group-Object |
Groups by key |
Compress-Archive |
Creates ZIP files |
In short
- PowerShell works with objects: file size and date are properties you can filter on.
- Always use
-WhatIfbefore moving or deleting. Group-Objectand theyyyy-MMformat allow archiving by period.Compress-Archivecreates ZIP files with no extra software.- Set the
RemoteSignedpolicy for the current user and document every script.