New-CabinetFile

Discussion in 'Scripting' started by GodHand, Sep 27, 2019.

  1. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    455
    544
    10
    A small function that will quickly create CAB files from any type of source.

    - CAB files can be used to apply packages, files, folders, data, custom deployment settings, etc. to Windows images, or just to save due to its lossless data compression.

    - Uses the StringBuilder .NET class to build the directive file used to create the CAB file due to its ability to concatenate and build very large strings. It then appends and formats the directive file.

    - High (LZX) compression can be selected. Default compression is MSZIP Fast compression.

    - Option to automatically remove the directive file written for the CAB creation.

    - Option to utilize the -PassThru switch to output the CAB creation process to the console.

    - Outputs an object that returns the path of the CAB file and the compression used.

    Code:
    Function New-CabinetFile
    {
    <#
        .SYNOPSIS
            Create compressed Cabinet (CAB) files.
      
        .DESCRIPTION
            Create compressed Cabinet (CAB) files utilizing lossless data compression.
      
        .PARAMETER SourcePath
            The path to the file, folder or directory that will be compressed into a CAB file.
      
        .PARAMETER CabinetPath
            The directory where the CAB file will be created.
      
        .PARAMETER HighCompression
            Enables LZX High Compression (Slower). Default is MSZIP Fast Compression.
      
        .PARAMETER RemoveDirective
            Automatically removes the directive file used to create the CAB file.
      
        .PARAMETER PassThru
            Outputs the CAB creation results to the console.
      
        .EXAMPLE
            PS C:\> New-CabinetFile -SourcePath "C:\Tools and Utilities" -CabinetPath $Env:SystemRoot -HighCompression
      
        .EXAMPLE
            PS C:\> New-CabinetFile -SourcePath "C:\Tools and Utilities" -CabinetPath $HOME\Desktop -RemoveDirective -Passthru
      
        .OUTPUTS
            PSCustomObject
    #>
      
        [CmdletBinding()]
        [OutputType([PSCustomObject])]
        Param
        (
            [Parameter(Mandatory = $true,
                HelpMessage = 'The path to the file, folder or directory that will be compressed into a CAB file.')]
            [ValidateScript( { Test-Path -Path (Resolve-Path -Path $_) })]
            [string]$SourcePath,
            [Parameter(Mandatory = $true,
                HelpMessage = 'The directory where the CAB file will be saved to.')]
            [ValidateScript( { Test-Path -Path (Resolve-Path -Path $_) -PathType Container })]
            [string]$CabinetPath,
            [Parameter(HelpMessage = 'Enables LZX High Compression (Slower). Default is MSZIP Fast Compression.')]
            [switch]$HighCompression,
            [Parameter(HelpMessage = 'Automatically removes the directive file used to create the CAB file.')]
            [switch]$RemoveDirective,
            [Parameter(HelpMessage = 'Outputs the CAB creation results to the console.')]
            [switch]$PassThru
        )
      
        Begin
        {
            $SourcePath = Get-Item -LiteralPath $SourcePath | Select-Object -ExpandProperty FullName
            $SourceName = Get-Item -LiteralPath $SourcePath | Select-Object -ExpandProperty Name
            $CabinetPath = Get-Item -LiteralPath $CabinetPath | Select-Object -ExpandProperty FullName
            $CabinetName = [IO.Path]::ChangeExtension($SourceName, '.cab')
            $DDFPath = Join-Path -Path $CabinetPath -ChildPath ([IO.Path]::ChangeExtension($SourceName, '.ddf'))
            If ($HighCompression.IsPresent) { $Compression = 'LZX' } Else { $Compression = 'MSZIP' }
        }
        Process
        {
            $DDFString = [System.Text.StringBuilder]::New()
            [void]$DDFString.AppendLine(';*** MAKECAB Directives ***;')
            [void]$DDFString.AppendLine('.OPTION EXPLICIT')
            [void]$DDFString.AppendLine('.Set CabinetNameTemplate={0}' -f $CabinetName)
            [void]$DDFString.AppendLine('.Set DiskDirectory1={0}' -f $CabinetPath)
            [void]$DDFString.AppendLine('.Set Cabinet=ON')
            [void]$DDFString.AppendLine('.Set Compress=ON')
            [void]$DDFString.AppendLine('.Set CompressionType={0}' -f $Compression)
            [void]$DDFString.AppendLine('.Set CabinetFileCountThreshold=0')
            [void]$DDFString.AppendLine('.Set FolderFileCountThreshold=0')
            [void]$DDFString.AppendLine('.Set FolderSizeThreshold=0')
            [void]$DDFString.AppendLine('.Set MaxCabinetSize=0')
            [void]$DDFString.AppendLine('.Set MaxDiskFileCount=0')
            [void]$DDFString.AppendLine('.Set MaxDiskSize=0')
            [void]$DDFString.AppendLine('.Set RptFileName={0}' -f (Join-Path -Path $Env:TEMP -ChildPath setup.rpt))
            [void]$DDFString.AppendLine('.Set InfFileName={0}' -f (Join-Path -Path $Env:TEMP -ChildPath setup.inf))
            Get-ChildItem -Path $SourcePath -Recurse | Unblock-File
            Get-ChildItem -Path $SourcePath -Recurse | Where-Object { (!$_.PsIsContainer) } | Select-Object -ExpandProperty Fullname | Foreach-Object -Process { [void]$DDFString.AppendLine("""$_"" ""$($_.SubString($SourcePath.Length + 1))""") }
            $DDFString.ToString() | Out-File -FilePath $DDFPath -Encoding UTF8
            If ($PassThru.IsPresent) { $RET = Start-Process -FilePath MAKECAB -ArgumentList ('/F "{0}"' -f $DDFPath) -Wait -PassThru }
            Else { $RET = Start-Process -FilePath MAKECAB -ArgumentList ('/F "{0}"' -f $DDFPath) -WindowStyle Hidden -Wait -PassThru }
            If ($RET.ExitCode -eq 0 -and (Test-Path -Path (Join-Path -Path $CabinetPath -ChildPath $CabinetName)))
            {
                [PSCustomObject]@{
                    Path        = $(Join-Path -Path $CabinetPath -ChildPath $CabinetName)
                    Compression = $Compression
                }
            }
            ElseIf ($RET.ExitCode -ne 0) { Write-Warning ('MAKECAB exited with exit code: {0}' -f $MakeCAB.ExitCode); Break }
        }
        End
        {
            If ($RemoveDirective.IsPresent) { Remove-Item -Path $DDFPath -Force -ErrorAction SilentlyContinue }
            @((Join-Path -Path $Env:TEMP -ChildPath setup.rpt), (Join-Path -Path $Env:TEMP -ChildPath setup.inf)) | Remove-Item -Force -ErrorAction SilentlyContinue
        }
    }
    
     
  2. abbodi1406

    abbodi1406 MDL KB0000001

    Feb 19, 2011
    9,823
    37,261
    300
    Maybe adding this would be better for higher compression :)
    Code:
    [void]$DDFString.AppendLine('.Set CompressionMemory=21')
    it has no affect on MSZIP, so it can always be in the directive

    thanks
     
  3. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    455
    544
    10
    My personal function has a very large directive file, but I left a lot out mostly for the sake of flexibility for anyone simply looking to quickly create CAB files. I did not want to overcomplicate it too much with a huge string of directive variables and the like that may confuse users who perhaps are not well versed on how directive files work.

    Quite frankly, though, to truly make optimal CAB files, I always use CABARC. Not only can it compress at high memory faster but it allows for both the inclusion and setting of file structures within the cabinet files.

    But with MAKECAB using LZX compression I believe its default CompressionMemory is 18 with a default window size 218 bytes (256KB). Moreover, you can slightly improve MSZIP compression by adding FolderSizeThreshold=1 which basically changes it to a PKZIP-compatible compression.

    I can add more complicated directive file examples that I have when I get home and add some more advanced variables to the function.