Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active March 16, 2026 16:38
Show Gist options
  • Select an option

  • Save scriptingstudio/81c74d4a3af59fa917407f568c288494 to your computer and use it in GitHub Desktop.

Select an option

Save scriptingstudio/81c74d4a3af59fa917407f568c288494 to your computer and use it in GitHub Desktop.
Simple PowerShell script compressor. The script works like "ConvertTo-Json -Compress".
<#
.SYNOPSIS
Removes comments and extra white space from a PS script.
.DESCRIPTION
Omits white space and indented formatting in the output.
.PARAMETER Path
Specifies the path to the PS file to compress.
.PARAMETER ScriptBlock
Specifies the PowerShell scriptblock to compress.
.PARAMETER NoTest
This parameter allows to skip a file path test. Useful for internal cases to avoid multiple checks.
.LINK
http://www.leeholmes.com/blog/2007/11/07/syntax-highlighting-in-powershell/
#>
function Compress-ScriptBlock {
[CmdletBinding(DefaultParameterSetName = 'File')]
param (
[Parameter(Position = 0, Mandatory, ParameterSetName = 'File')]
[ValidateNotNullOrEmpty()]
[alias('FilePath','FileName')][String] $Path,
[Parameter(Position = 0, Mandatory, ParameterSetName = 'ScriptBlock')]
[ValidateNotNullOrEmpty()]
[ScriptBlock] $ScriptBlock,
[Parameter(ParameterSetName = 'File')]
[Switch] $NoTest
)
if ($Path) {
if (-not $NoTest) {if (-not (Test-Path $Path)) {return}}
$ScriptBlockString = [IO.File]::ReadAllText((Resolve-Path $Path).Path)
$ScriptBlock = [ScriptBlock]::Create($ScriptBlockString)
} else {
# Convert the scriptblock to a string so that it can be referenced to extract script lines
$ScriptBlockString = $ScriptBlock.ToString()
}
# Tokenize the scriptblock and return all tokens except for comments
$Tokens = @([System.Management.Automation.PSParser]::Tokenize($ScriptBlock, [Ref] $Null)).Where{$_.Content -match '^#Requires' -or $_.Type -ne 'Comment'}
# Preallocate memory to avoid dynamic allocations
#$capacity = ($Tokens.Length | Measure-Object -Sum).Sum
$strBuilder = [System.Text.StringBuilder]::new($ScriptBlockString.Length)
$CurrentColumn = 1
$newLine = $false
$prevToken = $null
$joinLine = $false # indicates to join the previous line
$i = 0 # token index to look ahead
$null = foreach ($CurrentToken in $Tokens) {
if ($CurrentToken.Type -match '^NewLine|^LineContinuation') {
$CurrentColumn = 1
# Only insert a single newline. Sequential newlines are ignored in order to save space
if (-not $newLine) {
# experimental; TODO
if ($prevToken.Content -match '[\{\(]$') {}
elseif ($prevToken.Type -eq 'groupEnd') {$joinLine = $true} # skip extra newline
# needs to know the next token's type; if groupEnd, skip extra newline
elseif ($Tokens[$i+1].Type -eq 'groupEnd') {}
else {$strBuilder.AppendLine()}
$newLine = $true
}
} else {
$newLine = $false
# Do any indenting
if ($CurrentColumn -lt $CurrentToken.StartColumn) {
# Insert a single space in between tokens on the same line.
# Extraneous whiltespace is ignored.
if ($CurrentColumn -ne 1) {
$strBuilder.Append(' ')
}
}
# Insert newline inside token group
# experimental; TODO
if ($joinLine -and $prevToken.Type -eq 'NewLine' -and $CurrentToken.Type -ne 'groupEnd') {
#if ($Tokens[$i+1].Type -eq 'NewLine') {$strBuilder.Append(';')} else {$strBuilder.AppendLine()}
$strBuilder.AppendLine()
$joinLine = $false
}
# Handle multi-line strings
$tokenBody = $ScriptBlockString.Substring($CurrentToken.Start,$CurrentToken.Length)
if ($CurrentToken.Type -eq 'String' -and $CurrentToken.EndLine -gt $CurrentToken.StartLine) {
$strBuilder.Append($tokenBody.replace("`r`n",""))
} else { # Write out a regular token
$strBuilder.Append($tokenBody)
}
# Update our position in the column
$CurrentColumn = $CurrentToken.EndColumn
} # token type
$prevToken = $CurrentToken
$i++
} # token iterator
$strBuilder.ToString() #.replace("`r`n`r`n","")
} # END Compress-ScriptBlock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment