Recently I discovered a neat little utility called WizTree, which can be used to report on space used by files and folders on a drive. There are many utilities out there that can do that, but this one supports running on the command line which makes it very useful for scripting scenarios. It also works extremely quickly because it uses the Master File Table on disk instead of the slower Windows / .Net methods.
I wanted to create a disk usage report for systems that have less than 20GB of free space – the recommended minimum for doing a Windows 10 in-place upgrade – so that I can easily review it and identify files / folders that could potentially be deleted to free space on the disk. I wanted to script it so that it can be run in the background and deployed via ConfigMgr, and the resulting reports copied to a server share for review.
The following script does just that.
First, it runs WizTree on the command line and generates two CSV reports, one each for all files and folders on the drive. Next, since the generated CSV files contain sizes in bytes, the script imports the CSVs, converts the size data to include KB, MB and GB, then outputs to 2 new CSV files.
The script then generates 2 custom HTML reports that contain a list of the largest 100 files and folders, sorted by size.
Next it generates an HTML summary report that shows visually how much space is used on the disk and tells you how much space you need to free up to drop under the minimum 20GB-free limit.
Finally, it copies those reports to a server share, which will look like this:
The Disk Usage Summary report will look something like this:
And here’s a snippet from the large directories and files reports:
There are also CSV reports which contain the entire list of files and directories on the drive:
To use the script, simply download the WizTree Portable app, extract the WizTree64.exe and place it in the same location as the script (assuming 64-bit OS). Set the run location in the script (ie $PSScriptRoot if calling the script, or the directory location if running in the ISE), the temporary location where it can create files, and the server share where you want to copy the reports to. Then just run the script in admin context.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Script to export html and csv reports of file and directory content on the system drive | |
# Use to identify large files/directories for disk space cleanup | |
# Uses WizTree portable to quickly retrieve file and directory sizes from the Master File Table on disk | |
# Download and extract the WizTree64.exe and place in the same directory as this script | |
# Set the running location | |
$RunLocation = $PSScriptRoot | |
#$RunLocation = "C:\temp" | |
$TempLocation = "C:\temp" | |
# Set Target share to copy the reports to | |
$TargetRoot = "\\server-01\sharename\DirectorySizeInfo" | |
# Free disk space thresholds (percentages) for summary report | |
$script:Thresholds = @{} | |
$Thresholds.Warning = 80 | |
$Thresholds.Critical = 90 | |
# Custom function to exit with a specific code | |
function ExitWithCode | |
{ | |
param | |
( | |
$exitcode | |
) | |
$host.SetShouldExit($exitcode) | |
exit | |
} | |
# Function to set the progress bar colour based on the the threshold value in the summary report | |
function Set-PercentageColour { | |
param( | |
[int]$Value | |
) | |
If ($Value -lt $Thresholds.Warning) | |
{ | |
$Hex = "#00ff00" # Green | |
} | |
If ($Value -ge $Thresholds.Warning -and $Value -lt $Thresholds.Critical) | |
{ | |
$Hex = "#ff9900" # Amber | |
} | |
If ($Value -ge $Thresholds.Critical) | |
{ | |
$Hex = "#FF0000" # Red | |
} | |
Return $Hex | |
} | |
# Define Html CSS style | |
$Style = @" | |
<style> | |
table { | |
border-collapse: collapse; | |
} | |
td, th { | |
border: 1px solid #ddd; | |
padding: 8px; | |
} | |
th { | |
padding-top: 12px; | |
padding-bottom: 12px; | |
text-align: left; | |
background-color: #4286f4; | |
color: white; | |
} | |
</style> | |
"@ | |
# Set the filenames of WizTree csv's | |
$FilesCSV = "Files_$(Get-Date -Format 'yyyyMMdd_hhmmss').csv" | |
$FoldersCSV = "Folders_$(Get-Date -Format 'yyyyMMdd_hhmmss').csv" | |
# Set the filenames of customised csv's | |
$ExportedFilesCSV = "Exported_Files_$(Get-Date -Format 'yyyyMMdd_hhmmss').csv" | |
$ExportedFoldersCSV = "Exported_Folders_$(Get-Date -Format 'yyyyMMdd_hhmmss').csv" | |
# Set the filenames of html reports | |
$ExportedFilesHTML = "Largest_Files_$(Get-Date -Format 'yyyyMMdd_hhmmss').html" | |
$ExportedFoldersHTML = "Largest_Folders_$(Get-Date -Format 'yyyyMMdd_hhmmss').html" | |
$SummaryHTMLReport = "Disk_Usage_Summary_$(Get-Date -Format 'yyyyMMdd_hhmmss').html" | |
# Run the WizTree portable app | |
Start-Process -FilePath "$RunLocation\WizTree64.exe" -ArgumentList """$Env:SystemDrive"" /export=""$TempLocation\$FilesCSV"" /admin 1 /sortby=2 /exportfolders=0" -Verb runas -Wait | |
Start-Process -FilePath "$RunLocation\WizTree64.exe" -ArgumentList """$Env:SystemDrive"" /export=""$TempLocation\$FoldersCSV"" /admin 1 /sortby=2 /exportfiles=0" -Verb runas -Wait | |
#region Files | |
# Remove the first 2 rows from the CSVs to leave just the relevant data | |
$CSVContent = Get-Content -Path $TempLocation\$FilesCSV -ReadCount 0 | |
$CSVContent = $CSVContent | Select -Skip 1 | |
$CSVContent = $CSVContent | Select -Skip 1 | |
# Create a table to store the results | |
$Table = [System.Data.DataTable]::new("Directory Structure") | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Name",[System.String])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (Bytes)",[System.Int64])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (KB)",[System.Decimal])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (MB)",[System.Decimal])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (GB)",[System.Decimal])) | |
# Populate the table from the CSV data | |
Foreach ($csvrow in $CSVContent) | |
{ | |
$Content = $csvrow.split(',') | |
[void]$Table.rows.Add(($Content[0].Replace('"','')),$Content[2],([math]::Round(($Content[2] / 1KB),2)),([math]::Round(($Content[2] / 1MB),2)),([math]::Round(($Content[2] / 1GB),2))) | |
} | |
# Export the table to a new CSV | |
$Table | Sort 'Size (Bytes)' -Descending | Export-CSV -Path $TempLocation\$ExportedFilesCSV -NoTypeInformation -UseCulture | |
# Export the largest 100 results into html format | |
$Table | | |
Sort 'Size (Bytes)' -Descending | | |
Select -First 100 | | |
ConvertTo-Html -Property 'Name','Size (Bytes)','Size (KB)','Size (MB)','Size (GB)' -Head $style -Body "<h2>100 largest files on $env:COMPUTERNAME</h2>" -CssUri "http://www.w3schools.com/lib/w3.css" | | |
Out-String | Out-File $TempLocation\$ExportedFilesHTML | |
#endregion | |
#region Folders | |
# Remove the first 2 rows from the CSVs to leave just the relevant data | |
$CSVContent = Get-Content -Path $TempLocation\$FoldersCSV -ReadCount 0 | |
$CSVContent = $CSVContent | Select -Skip 1 | |
$CSVContent = $CSVContent | Select -Skip 1 | |
# Create a table to store the results | |
$Table = [System.Data.DataTable]::new("Directory Structure") | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Name",[System.String])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (Bytes)",[System.Int64])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (KB)",[System.Decimal])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (MB)",[System.Decimal])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Size (GB)",[System.Decimal])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Files",[System.String])) | |
[void]$Table.Columns.Add([System.Data.DataColumn]::new("Folders",[System.String])) | |
# Populate the table from the CSV data | |
Foreach ($csvrow in $CSVContent) | |
{ | |
$Content = $csvrow.split(',') | |
[void]$Table.rows.Add($($Content[0].Replace('"','')),$Content[2],([math]::Round(($Content[2] / 1KB),2)),([math]::Round(($Content[2] / 1MB),2)),([math]::Round(($Content[2] / 1GB),2)),$Content[5],$Content[6]) | |
} | |
# Export the table to a new CSV | |
$Table | Sort 'Size (Bytes)' -Descending | Export-CSV -Path $TempLocation\$ExportedFoldersCSV -NoTypeInformation -UseCulture | |
# Export the largest 100 results into html format | |
$Table | | |
Sort 'Size (Bytes)' -Descending | | |
Select -First 100 | | |
ConvertTo-Html -Property 'Name','Size (Bytes)','Size (KB)','Size (MB)','Size (GB)','Files','Folders' -Head $style -Body "<h2>100 largest directories on $env:COMPUTERNAME</h2>" -CssUri "http://www.w3schools.com/lib/w3.css" | | |
Out-String | Out-File $TempLocation\$ExportedFoldersHTML | |
#endregion | |
#region Create HTML disk usage summary report | |
# Get system drive data | |
$WMIDiskInfo = Get-CimInstance -ClassName Win32_Volume -Property Capacity,FreeSpace,DriveLetter | Where {$_.DriveLetter -eq $env:SystemDrive} | Select Capacity,FreeSpace,DriveLetter | |
$DiskInfo = [pscustomobject]@{ | |
DriveLetter = $WMIDiskInfo.DriveLetter | |
'Capacity (GB)' = [math]::Round(($WMIDiskInfo.Capacity / 1GB),2) | |
'FreeSpace (GB)' = [math]::Round(($WMIDiskInfo.FreeSpace / 1GB),2) | |
'UsedSpace (GB)' = [math]::Round((($WMIDiskInfo.Capacity / 1GB) – ($WMIDiskInfo.FreeSpace / 1GB)),2) | |
'Percent Free' = [math]::Round(($WMIDiskInfo.FreeSpace * 100 / $WMIDiskInfo.Capacity),2) | |
'Percent Used' = [math]::Round((($WMIDiskInfo.Capacity – $WMIDiskInfo.FreeSpace) * 100 / $WMIDiskInfo.Capacity),2) | |
} | |
# Create html header | |
$html = @" | |
<!DOCTYPE html> | |
<html> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css"> | |
<body> | |
"@ | |
# Set html | |
$html = $html + @" | |
<h2>Disk Space Usage for Drive $($DiskInfo.DriveLetter) on $env:COMPUTERNAME</h2> | |
<table cellpadding="0" cellspacing="0" width="700"> | |
<tr> | |
<td style="background-color:$(Set-PercentageColour -Value $($DiskInfo.'Percent Used'));padding:10px;color:#ffffff;" width="$($DiskInfo.'Percent Used')%"> | |
$($DiskInfo.'UsedSpace (GB)') GB ($($DiskInfo.'Percent Used') %) | |
</td> | |
<td style="background-color:#eeeeee;padding-top:10px;padding-bottom:10px;color:#333333;" width="$($DiskInfo.'Percent Used')%"> | |
</td> | |
</tr> | |
</table> | |
<table cellpadding="0" cellspacing="0" width="700"> | |
<tr> | |
<td style="padding:5px;" width="80%"> | |
Capacity: $($DiskInfo.'Capacity (GB)') GB | |
</td> | |
</tr> | |
<tr> | |
<td style="padding:5px;" width="80%"> | |
FreeSpace: $($DiskInfo.'FreeSpace (GB)') GB | |
</td> | |
</tr> | |
<tr> | |
<td style="padding:5px;" width="80%"> | |
Percent Free: $($DiskInfo.'Percent Free') % | |
</td> | |
</tr> | |
</table> | |
"@ | |
If ($DiskInfo.'FreeSpace (GB)' -lt 20) | |
{ | |
$html = $html + @" | |
<table cellpadding="0" cellspacing="0" width="700"> | |
<tr> | |
<td style="padding:5px;color:red;font-weight:bold" width="80%"> | |
You need to free $(20 – $DiskInfo.'FreeSpace (GB)') GB on this disk to pass the W10 readiness check! | |
</td> | |
</tr> | |
</table> | |
"@ | |
} | |
# Close html document | |
$html = $html + @" | |
</body> | |
</html> | |
"@ | |
# Export to file | |
$html | | |
Out-string | | |
Out-File $TempLocation\$SummaryHTMLReport | |
#endregion | |
#region Copy files to share | |
# Create a subfolder with computername if doesn't exist | |
If (!(Test-Path $TargetRoot\$env:COMPUTERNAME)) | |
{ | |
$null = New-Item -Path $TargetRoot -Name $env:COMPUTERNAME -ItemType Directory | |
} | |
# Create a subdirectory with current date-time | |
$DateString = ((Get-Date).ToUniversalTime() | get-date -Format "yyyy-MM-dd_HH-mm-ss").ToString() | |
If (!(Test-Path $TargetRoot\$env:COMPUTERNAME\$DateString)) | |
{ | |
$null = New-Item -Path $TargetRoot\$env:COMPUTERNAME -Name $DateString -ItemType Directory | |
} | |
# Set final target location | |
$TargetLocation = "$TargetRoot\$env:COMPUTERNAME\$DateString" | |
# Copy files | |
$Files = @( | |
$ExportedFilesCSV | |
$ExportedFoldersCSV | |
$ExportedFilesHTML | |
$ExportedFoldersHTML | |
$SummaryHTMLReport | |
) | |
Try | |
{ | |
Robocopy $TempLocation $TargetLocation $Files /R:10 /W:5 /NP | |
} | |
Catch {} | |
#endregion | |
# Cleanup temp files | |
$Files = @( | |
$FilesCSV | |
$FoldersCSV | |
$ExportedFilesCSV | |
$ExportedFoldersCSV | |
$ExportedFilesHTML | |
$ExportedFoldersHTML | |
$SummaryHTMLReport | |
) | |
Foreach ($file in $files) | |
{ | |
Remove-Item -Path $TempLocation\$file -Force | |
} | |
# Force a code 0 on exit, in case of some non-terminating error. | |
ExitWithCode 0 |
Great script! But how to let the script run an analyze of a d: drive?
D:\
How do you deployed this via configmgr
Just leaving a comment in case someone else finds this script and it does not work:
I had to modify lines 103-107 and 139-145 as follows:
$Table = New-Object System.Data.DataTable “Directory Structure”
$newcol = New-Object System.Data.DataColumn Name,([System.String]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (Bytes)”,([System.Int64]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (KB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (MB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (GB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$Table = New-Object System.Data.DataTable “Directory Structure”
$newcol = New-Object System.Data.DataColumn Name,([System.String]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (Bytes)”,([System.Int64]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (KB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (MB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn “Size (GB)”,([System.Decimal]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn Files,([System.String]);$Table.Columns.Add($newcol)
$newcol = New-Object System.Data.DataColumn Folders,([System.String]);$Table.Columns.Add($newcol)