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.
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.
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.
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.
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?
Commands in Try must throw terminating exceptions. I usually include -erroraction Stop with each cmdlet I am running.
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?
The erroraction parameter works on a more granular per cmdlet level and you need it for error handling.
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!