Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Compress Files By Extension

Posted on October 3, 2011

I never seem to build my test virtual machines with enough disk space. Which means at some point I start dealing with space issues and resort to all sorts of hacks to buy myself a little time. One thing I do is look for files that I can compact using NTFS compression. For examples text files and most Office files compress pretty well. Thus the challenge is to find those files and compress them.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

The WMI CIM_DATAFILE class has a Compress() method (as well as Uncompress()). So all I have to do is construct a query to find the appropriate files and then invoke the Compress() method for each result. I have found in the past that when querying CIM_DATAFILE to be as specific as possible, taking special care to filter on the drive. The resulting query can get a tad complicated.

[cc lang="PowerSHell"]
Select * from CIM_DATAFILE where Drive='c:' AND Path Like '%\\work\\%'AND Compressed='FALSE' AND FileSize >= 4096 AND (Extension='txt' OR Extension='doc' OR Extension='xml')
[/cc]

This query will search for all TXT, DOC and XML files anywhere under C:\Work that are over 4KB in size. I have found that compressing files smaller than 4KB is counter-productive. This query will take a while to run. It would make a nice background job.

[cc lang="PowerShell"]
get-wmiobject CIM_DATAFILE -filter "Drive='c:' AND Path Like '%\\work\\%'AND Compressed='FALSE' AND FileSize >= 4096 AND (Extension='txt' OR Extension='doc' OR Extension='xml')" -asjob
[/cc]

When the job is complete, the results can be piped to Invoke-WMIMethod. The benefit is that the cmdlet supports -Whatif.

[cc lang="PowerShell"]
$files=receive-job 1 -keep
$files | invoke-wmimethod -name Compress -whatif
[/cc]

Because constructing the query is the most complicated part, I put together a script to do all the hard work for me.

[cc lang="PowerShell"]
#requires -version 2.0

[cmdletbinding(SupportsShouldProcess=$True)]

Param (
[Parameter(Mandatory=$True,HelpMessage="Enter a file extension, without the period")]
[ValidateNotNullorEmpty()]
[string[]]$Extension,
[ValidatePattern("^[a-zA-Z]:")]
[string]$Path="c:\",
[ValidateScript({$_ -ge 4KB})]
[int]$Size=4KB,
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername,
[switch]$Uncompress
)

Write-Verbose "Starting $($myinvocation.mycommand)"

#get first two characters for drive
$Drive=$Path.Substring(0,2)

Write-Verbose "Connecting to drive $drive on $computername"
Write-Verbose "Finding files >= than $size"

#build query
$extensions=$extension -as [string]
#parse the extensions and build a complex OR structure for each one
$extFilter="(Extension='{0}{1}" -f ($extensions.replace(" ","' OR Extension='")),"')"

if ($Uncompress) {
Write-Verbose "Uncompressing files"
$Compress="TRUE"
}
else {
$Compress="FALSE"
}

#split off the folder portion of the path, change \ to \\
$folder=$Path.Split(":")[1].Replace("\","\\")

$filter="Drive='$Drive' AND Path Like '%$Folder\\%'AND Compressed='$Compress' AND FileSize >= $size AND $extFilter"
#define a subset of properties to return in hopes of tweaking performance
$properties="Drive,Path,Compressed,FileSize,Extension,CSName,Name"

Write-Verbose "Query = $filter"

Write-Progress -Activity "Compress File Types" -status "Running filter $filter on $computername" `
-currentoperation "Searching $Path. Please wait...this may take several minutes..."

#capture time before the WMI Query
$start=Get-Date

Try {
Write-Verbose "Getting files from $Path"
$files=Get-WmiObject -class CIM_DATAFILE -filter $filter -Property $properties -computername $computername -ErrorAction Stop
}
Catch {
Write-Error ("Failed to run query on $computername. Exception: {0}" -f $_.Exception.Message)
}

$filecount=($files | measure-object).count
Write-Verbose "Found $filecount files"

#define some variables for Write-Progress
if ($Uncompress) {
$activity="Uncompress File Types"
$status="Uncompressing"
}
else {
$activity="Compress File Types"
$status="Compressing"
}

#only process if files were returned
if ($filecount -gt 0) {
#keep a running count of compressed files
$iCompressed=0
$iFailures=0
$i=0
Write-Verbose "Processing files"

foreach ($file in $files) {
$i++
[int]$percent=($i/$filecount)*100
Write-Verbose $file.name

Write-Progress -Activity $activity -Status $status -currentoperation $file.name -percentComplete $percent
if ($pscmdlet.ShouldProcess($file.name)) {
if ($Uncompress) {
$rc=$file.Uncompress()
}
else {
$rc=$file.Compress()
}
Switch ($rc.returnvalue) {
0 { $Result="The request was successful"
#increment the counter
$iCompressed++
#write the WMI file object to the pipeline
#for later processing
write $file | select @{Name="Computername";Expression={$_.CSName}},
Name,FileSize,Extension

Break
}

2 { $Result="Access was denied." ; Break}
8 { $Result="An unspecified failure occurred."; Break}
9 { $Result="The name specified was invalid."; Break}
10 { $Result="The object specified already exists."; Break}
11 { $Result="The file system is not NTFS."; Break}
12 { $Result="The operating system is not supported."; Break}
13 { $Result="The drive is not the same."; Break}
14 { $Result="The directory is not empty."; Break}
15 { $Result="There has been a sharing violation." ; Break}
16 { $Result="The start file specified was invalid."; Break}
17 { $Result="A privilege required for the operation is not held."; Break}
21 { $Result="A parameter specified is invalid."; Break}

} #end Switch

#display a warning message if there was a problem
if ($rc.returnvalue -ne 0) {
$msg="Error compressing: {0}. {1}" -f $file.name,$Result
Write-Warning $msg
$iFailures++
}
Write-Verbose "Result=$result"
} #if should process
} #foreach

#get the time after all files have been compressed/uncompressed
$End=Get-Date
[string]$RunTime=($End=$Start)
Write-Verbose "Presenting summary"

Write-Progress -Activity "Compress File Types" -status "Finished" -completed $True

Write-Host "`n$Status Summary" -ForegroundColor CYAN
Write-Host "**************************************" -ForegroundColor CYAN
Write-Host "Computer : $computername" -foregroundcolor CYAN
Write-Host "File types : $extensions" -foregroundcolor CYAN
Write-Host "Minimum Size : $size" -ForegroundColor CYAN
Write-Host "Path : $path" -foregroundcolor CYAN
Write-Host "Total Files : $filecount" -foregroundcolor CYAN
Write-Host "Success : $iCompressed" -foregroundcolor CYAN
Write-Host "Failures : $iFailures" -foregroundcolor CYAN
Write-Host "Runtime : $Runtime" -ForegroundColor CYAN

}
else {
Write-Warning "No matching files ($extensions) found in $Path on $computername with a minimum size of $size."
}

#end of script
[/cc]

The script takes an array of file extensions, without the periods and a path. The default is the entire C:\ drive. Optionally, you can specify a remote computer.

[cc lang="DOS"]
PS C:\> c:\scripts\compress-filetype "txt,","ps1","log" -path c:\files -size 10kb -comp FILE01
[/cc]

The script parses the parameters into a WMI filter. The resulting files are processed one at at time and I invoke the Compress() method for each file. I could have used Invoke-WMIMethod, but I wrote my own -Whatif code because I want to do a number of things with each file if it is compressed such as keeping a running tally of successes and failure. Also note my use of the Switch construct to decode the result value. At the end of the script, a summary is written using Write-Host. The script's pipelined output is a subset of file information.

[cc lang="PowerShell"]
#write the WMI file object to the pipeline
write $file | select @{Name="Computername";Expression={$_.CSName}},Name,FileSize,Extension
[/cc]

Even though this is a script and not a function, you can still take advantage of cmdletbinding so you get features like -Verbose and -Whatif.

The complete file includes comment based help. Download Compress-FileType and let me know what you think.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

7 thoughts on “Compress Files By Extension”

  1. Scott says:
    October 5, 2011 at 12:32 am

    I’ve been following you but not really following very well. I don’t understand why you would do this instead of right-clicking on the disk, hit properties, and check the block to compress the disk. Is my method too crude? I don’t know all of the WMIs. It really makes me a little scared because I know it is only going to get more and more important to learn PowerShell.

    1. Jeffery Hicks says:
      October 5, 2011 at 7:40 am

      There’s nothing wrong with compressing the disk using the GUI, or even a folder. Although some files can’t be compressed any further and compressing small files isn’t productive in my opinion. I’m demonstrating how you can use WMI for a more granular approach. Plus, you could remotely compress files on one or more computers without any type of interactive or console logon. Thanks for reading and your comment.

  2. Conrad says:
    December 14, 2011 at 3:41 pm

    I have used try/catch before without any issues, so I am not sure what is going on here…

    Try{

    remove-item \\$name\c$\windows\temp\filename.exe
    }
    Catch {Write “Not able to access files on $name”}

    It always throws an error for the remove-item and it never catches it…. Am I missing something simple?

    1. Jeffery Hicks says:
      December 14, 2011 at 4:00 pm

      Commands in Try must throw terminating exceptions. I usually include -erroraction Stop with each cmdlet I am running.

      1. Conrad says:
        December 14, 2011 at 4:05 pm

        Awesome works perfectly. I had thought that -ea stop would stop the whole script if there was an error on that line, but it looks like it just stops that line?

      2. Jeffery Hicks says:
        December 14, 2011 at 4:10 pm

        The erroraction parameter works on a more granular per cmdlet level and you need it for error handling.

  3. Conrad says:
    December 14, 2011 at 4:16 pm

    Yeah I use erroraction sometimes however I have never used the “stop” with it though. I guess I should also look a bit more into terminating and non-terminating errors. I ordered your book TFM a week ago or so and I can’t wait for it to get here. I can normally get by with my powershell knowledge but I want to get a lot better with it. Thanks for your help!

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d