I'm not sure where this idea came from, but I thought it might be nice to redirect output to a compressed text file. I know disk space is cheap these days but perhaps you're running PowerShell 2.0 on an older platform and you want to save output from a command that will generate a ton of data. Using NTFS compression might buy you a little breathing room. My function, Out-CompressedFile, can be used in place of Out-File.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
My function doesn't have all the features of Out-File and assumes you are using most of the defaults.
[cc lang="PowerShell"]
Function Out-CompressedFile {
[cmdletBinding(SupportsShouldProcess=$True)]
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a file path and name",
ValueFromPipeline=$False)]
[ValidateNotNullorEmpty()]
[string]$FilePath,
[Parameter(Position=1,Mandatory=$False,ValueFromPipeline=$True)]
[psobject]$InputObject,
[switch]$Append
)
Begin {
Write-Verbose -Message "$(Get-Date) Starting $($myinvocation.mycommand)"
#get file if -append was specified
if ($Append -AND (Test-Path $FilePath))
{
Write-Verbose -Message "$(Get-Date) Appending to $Filepath"
#get full path
if (-Not $FilePath.Contains("\"))
{
$FilePath=(Get-Item $filePath).fullname
}
}
elseif (-Not $Append)
{
#otherwise, create an empty file
Write-Verbose -Message "$(Get-Date) Creating $FilePath"
#if just a file name, then use the current working directory"
if (-not $FilePath.Contains("\"))
{
Write-Verbose -Message "$(Get-Date) Using current working directory $PWD"
$FilePath=Join-Path -Path (Get-item $pwd).FullName -ChildPath $FilePath
}
if ($pscmdlet.ShouldProcess($filepath))
{
Try
{
#Create the file
$f=[System.IO.File]::Create($filepath)
$f.Close()
#get the full file name
$FilePath=$f.Name
} #close Try
Catch
{
Write-Warning "Failed to create $Filepath"
Write-Error $_
Exit
} #close Catch
} #close if should process
} #close elseif not $append
else
{
Write-Error "Failed to find $filepath. You can only append to an existing file."
Exit
}
Write-Verbose -Message "$(Get-Date) Full path is $FilePath"
#replace \ with \\ for WMI
$wmipath=$filePath.Replace("\","\\")
Write-Verbose -Message "$(Get-Date) Getting $wmipath"
#check and see if file is already compressed
$CIMFile=Get-WmiObject -Class CIM_DATAFILE -filter "Name='$wmipath'"
if (-Not $CIMFile.Compressed)
{
#if not, compress it
if ($pscmdlet.ShouldProcess("$Filepath"))
{
Write-Verbose -Message "$(Get-Date) Compressing $Filepath"
$rc=$CIMFile.Compress()
if ($rc.ReturnValue -ne 0)
{
Write-Warning "Failed to compress $Filepath. Continuing without it."
}
}
} #close if not compressed
#initialize a variable to hold all the input
Write-Verbose -Message "$(Get-Date) Initializing data variable"
$data=@()
} #close Begin
Process {
Foreach ($item in $InputObject) {
$data+=$item
}#close Foreach
} #close process
End {
Write-Verbose -Message "$(Get-Date) Sending output to $FilePath"
#data will always be appended because the file always exists, even it is empty
$data | Out-File -FilePath $Filepath -append
Write-Verbose -Message "$(Get-Date) Ending $($myinvocation.mycommand)"
} #close End
} #end Function
[/cc]
My function has comment-based help but I've omitted from the listing here.
You use the function just as you would Out-File with some subtle differences. First, if you use -Append, the function verifies the file exists. This is because I want to check and see if the file exists so I can enable compression if necessary. If you don't specify -Append, then I create an empty file.
[cc lang="PowerShell"]
Try
{
#Create the file
$f=[System.IO.File]::Create($filepath)
$f.Close()
#get the full file name
$FilePath=$f.Name
} #close Try
[/cc]
I also compress the file ahead of time if it is not already compressed using WMI. WMI needs \\ in a path for each \ so I fix the path accordingly.
[cc lang="PowerShell"]
#replace \ with \\ for WMI
$wmipath=$filePath.Replace("\","\\")
Write-Verbose -Message "$(Get-Date) Getting $wmipath"
#check and see if file is already compressed
$CIMFile=Get-WmiObject -Class CIM_DATAFILE -filter "Name='$wmipath'"
if (-Not $CIMFile.Compressed)
{
#if not, compress it
if ($pscmdlet.ShouldProcess("$Filepath"))
{
Write-Verbose -Message "$(Get-Date) Compressing $Filepath"
$rc=$CIMFile.Compress()
if ($rc.ReturnValue -ne 0)
{
Write-Warning "Failed to compress $Filepath. Continuing without it."
}
}
} #close if not compressed
[/cc]
Once I have the file ready, I take all input and store it in a buffer variable. To be honest, I'm not sure what the effect will be for extremely large data sets. But I expect this to work for 99% percent of you.
[cc lang="PowerShell"]
Process {
Foreach ($item in $InputObject) {
$data+=$item
}#close Foreach
} #close process
[/cc]
The last part of the function simply passes the data to Out-File, using the compressed file. Because the file has already been verified or created, I need to use -Append.
[cc lang="PowerShell"]
End {
Write-Verbose -Message "$(Get-Date) Sending output to $FilePath"
#data will always be appended because the file always exists, even it is empty
$data | Out-File -FilePath $Filepath -append
Write-Verbose -Message "$(Get-Date) Ending $($myinvocation.mycommand)"
} #close End
[/cc]
Use the function once it is loaded into your PowerShell session, just as you would Out-File.
Download Out-CompressedFile.
Shortcut syggestiuon:
$file="E:\projects\scripts\14193132471.txt"
dir $file |select mode,attributes |fl
[System.IO.File]::SetAttributes($file,[System.IO.FileAttributes]::Compressed)
dir $file |select mode,attributes |fl
That would certainly work. I’m so used to using WMI. Thanks for the tip.
I have to appologize for that. It dowsn;t wotk as I thought. You need to use IO-CONTROL API to do the compression. IOCTL is not exposed through NET classes. so my idea won’t work for the ‘Compressed’ attribute but will work for all others I have tried.
WMI would be the easied way to access the IOCTL function as far as I can tell.