powershell 脚本压缩工具

01 前言

写完了powershell脚本,有时想直接配合cmd直接用(传给其他人用也方便),而不是保存成.ps1文件的方式,这样就可以免去设置执行策略的过程。那么问题的关键就是怎么把N多行的powershell代码压缩成一行?网上搜了一圈,各种js/html/css代码压缩的一大堆了,没有找到powershell脚本的压缩,大概是因为这个语言还是比较小众的。没办法,自己动手吧(当然如果行数不多,手工拼成一行也OK)。于是参照JS压缩的代码写了一个,不过目前可能还不够完善。

02 正文

下面贴出来权当抛砖引玉。

<#
powershell 脚本压缩工具

参考JS压缩: http://tools.jb51.net/code/js_yasuo
by hokis
2019-09-09
#>

[CmdletBinding()]
param(
    [string]
    $filePath,

    #转为bat,需要注意的是,最好脚本代码里面全部使用单引号,少用甚至不用双引号
    [switch]
    $toBat,
    
    #是否自动添加行末的分隔符,如果脚本中有自定义函数,请慎用此开关,还不够完善
    [switch]
    $addEndWord
)

<#
.Synopsis
   添加行尾字符。其实比较麻烦,尤其是处理自定义函数的时候
   此处未完善,如果脚本中不包含自定义函数,可以尝试使用,否则,请慎用
#>
function Add-LineEndWord
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str,
        [string]
        $endWord=";"
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $newStr = New-Object System.Text.StringBuilder
        $str -split $newL | ForEach-Object{
            if($_ -and $_ -notmatch ('.*?['+$endWord+',\|\[\]\(\{\}]$')){
                [void]$newStr.AppendLine($_+$endWord)
            }else{
                [void]$newStr.AppendLine($_)
            }
        }
        return $newStr.ToString()
    }
}

<#
.Synopsis
   替换字符串
#>
function Replace-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $inQuote=$escaped = $false
        $c=$quoteChar=$literal= ''
        $t = New-Object System.Text.StringBuilder
        $str -split $newL | ForEach-Object{
            $j = 0
            $inQuote = $false
            foreach($c in $_.ToCharArray()){
                if(-not $inQuote){
                    #查找字符串开始位置
                    if ($c -eq '"' -or $c -eq ''''){
                        $inQuote = $true
                        $escaped = $false
                        $quoteChar = $c
                        $literal = $c
                    }else{
                        [void]$t.Append($c)
                    }
                }else{
                    #查找字符串结束位置
                    if($c -eq $quoteChar -and -not $escaped){
                        $inQuote = $false
                        $literal += $quoteChar
                        [void]$t.Append( '__'+ $Global:literalStrings.length+'__')
                        $Global:literalStrings += $literal
                    }elseif($c -eq '`' -and -not $escaped){
                        $escaped = $true
                    }else {
                        $escaped = $false
                    }
                    $literal += $c
                }
                $j++
            }
            [void]$t.AppendLine()
        }
        return $t.ToString()
    }
}

<#
.Synopsis
   去掉注释
#>
function Remove-Comment
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $newStr = New-Object System.Text.StringBuilder
        #去掉单行注释 # ,不得包括多行注释
        $str -split $newL | ForEach-Object{
            [void]$newStr.Append(($_ -replace '([^\x23]*[^\x3c]?)\x23[^\x3e]*$','$1'))
        }
        #去掉多行注释<#...#>
        $str = $newStr.ToString()
        [void]$newStr.Clear()
        $str -split '#>' | ForEach-Object{
            [void]$newStr.Append(($_ -replace '(.*)\x3c\x23(.*)$','$1 '))
        }     
        return $newStr.ToString() 
    }
}

<#
 .Synopsis
   压缩空格
#>
function Compress-WhiteSpace
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )

    Process
    {
        #清理空白字符
        $str = (($str -replace '\s+',' ') -replace '^\s(.*)','$1') -replace '(.*)\s$','$1'
        #[!%&()*+,/:;<=>?[]{|}]   减号(-)要注意,可能是命令的参数,不能随便移除两边的空格;in 后面的空格要谨慎
        $str = $str -replace '(?<!in)\s([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x7b\x7c\x7d])','$1'
        $str = $str -replace '([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x7b\x7c\x7d])\s','$1'
        return $str
    }
}

<#
.Synopsis
   连接字符串
#>
function Combine-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        return (($str -replace '"\+"','') -replace '''\+''','')
    }
}

<#
.Synopsis
   恢复字符串
#>
function Restore-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )

    Process
    {
        $i = 0
        $newStr = $str
        $Global:literalStrings | ForEach-Object{
            $newStr = $newStr -replace ('__{0}__' -f $i),$_
            $i++
        }
        return $newStr
    }
}
if(-not (Test-Path $filePath)){
    Write-Host "文件不存在!"
    exit
}

$Global:literalStrings = @()
$res= Get-Content -Path $filePath -Encoding Default
if($addEndWord){
	$res = Add-LineEndWord -str $res
}
$res = Replace-LiteralStrings -str $res
$res = Remove-Comment -str $res
$res = Compress-WhiteSpace -str $res
$res = Combine-LiteralStrings -str $res
$res = Restore-LiteralStrings -str $res

$fi = New-Object System.IO.FileInfo("$($filePath)")
$saveFile = $fi.DirectoryName + '\' + $fi.BaseName + '.min' + $fi.Extension
$newL = [System.Environment]::NewLine
if($toBat){
    #cmd里面,三个双引号才能表示poweershell中的一个双引号
    $res = $res -replace '"','"""'
    $res = "@echo off$($newL)cd /d %~dp0$($newL)powershell.exe -command `""+ $res + "`"$($newL)pause"
    $saveFile = $fi.DirectoryName + '\' + $fi.BaseName + '.min.bat'
}
$res | Out-File -FilePath $saveFile -Encoding default
Write-Host "Done..."
  • 使用说明:
    将以上代码保存为CompressPSScript.ps1,在PS环境下切换到此脚本所在路径,并执行:.\CompressPSScript.ps1 -filePath "d:\myscripts\test.ps1" 。其中d:\myscripts\test.ps1代表要压缩的powershell脚本的全路径(如果没有自定义函数,可以加-addEndWord开关自动在每句末尾加;,否则,请先自行在脚本中必要的位置添加;),则将在d:\myscripts\目录下生成test.min.ps1。如果加-toBat 开关,则生成test.min.bat文件。
  • 生成.bat文件格式时,默认会在头尾增加部分语句,如果不需要,请自行删除。
  • 压缩成一行的powershell代码怎么在CMD中直接用?参考这个写法:powershell.exe -command "XXX"。其中XXX应替换成实际的powershell代码段,这样就可以直接执行啦,不必考虑执行策略的问题。

附一张效果图:
压缩后的脚本制成BAT

03 后记

既然有压缩操作,那反操作——格式化(美化)的有没有?很遗憾,目前表示没有。因为简单看了下JS的美化,觉得还是有一定难度的,要建立语法树等等的一大堆,看着头都大,以后有机会再研究也说不定。或者哪里有现成的可以告知一下,十分感谢。
这个项目可以进行一定的美化处理,可以参考。

欢迎留言交流~
------END------

04 更新版本

2019-09-10 更新内容:

  • 改进了部分算法,提升速度
  • 使用方法同版本V1
<#
powershell 脚本压缩工具V2.1

参考JS压缩: http://tools.jb51.net/code/js_yasuo
by hokis
2019-09-10

#>

[CmdletBinding()]
param(
    [string]
    $filePath,

    #转为bat,需要注意的是,最好脚本代码里面全部使用单引号,少用甚至不用双引号
    [switch]
    $toBat,
    
    #是否自动添加行末的分隔符,如果脚本中有自定义函数,请慎用此开关,还不够完善
    [switch]
    $addEndWord
)

<#
.Synopsis
   添加行尾字符。其实比较麻烦,尤其是处理自定义函数的时候
   此处未完善,如果脚本中不包含自定义函数,可以尝试使用,否则,请慎用
#>
function Add-LineEndWord
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str,
        [string]
        $endWord=";"
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $newStr = New-Object System.Text.StringBuilder
        $str -split $newL | ForEach-Object{
            if($_ -and $_ -notmatch ('.*?['+$endWord+',\|\[\]\(\{\}]$')){
                [void]$newStr.AppendLine($_+$endWord)
            }else{
                [void]$newStr.AppendLine($_)
            }
        }
        return $newStr.ToString()
    }
}

<#
.Synopsis
   替换字符串
   返回两个值,第一个为处理后的字符串,第二个为被替换的字符串的值的集合
#>
function Replace-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $replaceList = New-Object System.Collections.ArrayList
        $count = 0
        $sb = New-Object System.Text.StringBuilder
        $lines = $str -split $newL
        foreach($line in $lines){
            $matches = [System.Text.RegularExpressions.Regex]::Matches($line,'(\x27(.*?)(?<!\x60)\x27)|(\x22(.*?)(?<!\x60)\x22)')
            if($matches.Count -gt 0){
                $lastIndex = 0
                foreach($it in $matches){
                    if($count -eq 0){   
                        [void]$sb.Append($line.Substring(0,$it.index)).Append(('__{0}__' -f $count))
                    }else{
                        [void]$sb.Append($line.Substring($lastIndex,$it.index-$lastIndex)).Append(('__{0}__' -f $count))   
                    }
                    $lastIndex = $it.index + $it.length
                    $count ++
                }
                [void]$sb.AppendLine($line.Substring($lastIndex))
                $replaceList.AddRange($matches)
            }else{
                [void]$sb.AppendLine($line)
            }
        }
        return @($sb.ToString(),$replaceList)
    }
}

<#
.Synopsis
   去掉注释
#>
function Remove-Comment
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        $newL = [System.Environment]::NewLine
        $newStr = New-Object System.Text.StringBuilder
        #去掉单行注释 # ,不得包括多行注释
        $str -split $newL | ForEach-Object{
            [void]$newStr.Append(($_ -replace '([^\x23]*[^\x3c]?)\x23[^\x3e]*$','$1'))
        }
        #去掉多行注释<#...#>
        $str = $newStr.ToString()
        [void]$newStr.Clear()
        $str -split '#>' | ForEach-Object{
            [void]$newStr.Append(($_ -replace '(.*)\x3c\x23(.*)$','$1 '))
        }     
        return $newStr.ToString() 
    }
}

<#
 .Synopsis
   压缩空格
#>
function Compress-WhiteSpace
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )

    Process
    {
        #清理空白字符
        $str = ((($str -replace '\s+',' ') -replace '^\s(.*)','$1') -replace '(.*)\s$','$1')
        #[!%&)*+,/:;<=>?]|}]   减号(-)要注意,可能是命令的参数,不能随便移除两边的空格
        $str = ($str -replace '\s([\x21\x25\x26\x29\x2a\x2b\x2c\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5d\x7c\x7d])','$1')
        #( [ { 之前的以-开头的都不能去掉空格;in 后面的空格要谨慎(foreach条件里面的in)
        $str = ($str -replace '(?<!in|\-[a-zA-Z0-9]*?\b)\s([\x28\x5b\x7b])','$1')
        $str = ($str -replace '([\x21\x25\x26\x28\x2a\x2b\x2c\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x7b\x7c])\s','$1')
        #) ] } 之后的以-开头的都不能去掉空格
        $str = ($str -replace '([\x29\x5d\x7d])\s(?!(\-[a-zA-Z]*)\b)','$1')

        return $str
    }
}

<#
.Synopsis
   连接字符串
   同时处理一些特别的情况比如:} for/foreach/if/switch/while() 要在}后加;
   } do/try{ 也要在}后加;
#>
function Combine-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str
    )
    Process
    {
        return (((($str -replace '"\+"','') -replace '''\+''','') -replace '(\x7d)((for|foreach|while|if|switch)\x28)','$1;$2') -replace '(\x7d)((do|try)\x7b)','$1;$2s')
    }
}

<#
.Synopsis
   恢复字符串
#>
function Restore-LiteralStrings
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0)]
        $str,

        [System.Collections.ArrayList]
        $list
    )

    Process
    {
        $i = 0
        $newStr = $str
        foreach($l in $list){
            $newStr = ($newStr -replace ('__{0}__' -f $i),$l)
            $i++
        }
        return $newStr
    }
}

if(-not (Test-Path $filePath)){
    Write-Host "文件不存在!"
    exit
}

$res= Get-Content -Path $filePath -Encoding Default
if($addEndWord){
	$res = Add-LineEndWord -str $res
}
$res,$list = Replace-LiteralStrings -str $res
$res = Remove-Comment -str $res
$res = Compress-WhiteSpace -str $res
$res = Combine-LiteralStrings -str $res
$res = Restore-LiteralStrings -str $res -list $list

$fi = New-Object System.IO.FileInfo("$($filePath)")
$saveFile = $fi.DirectoryName + '\' + $fi.BaseName + '.min' + $fi.Extension
$newL = [System.Environment]::NewLine
if($toBat){
    #cmd里面,三个双引号才能表示poweershell中的一个双引号
    $res = $res -replace '"','"""'
    $res = "@echo off$($newL)cd /d %~dp0$($newL)powershell.exe -command `""+ $res + "`"$($newL)pause"
    $saveFile = $fi.DirectoryName + '\' + $fi.BaseName + '.min.bat'
}
$res | Out-File -FilePath $saveFile -Encoding default
Write-Host "Done..."

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值