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代码段
,这样就可以直接执行啦,不必考虑执行策略的问题。
附一张效果图:
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..."
赞 (0)