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)

相关推荐

  • ​LeetCode刷题实战126:单词接龙 II

    算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试.所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 ! 今天和大家 ...

  • JavaScript中将字符串中的字母全部转换为大写字母/小写字母

    <script> let str = "Hello World!"; // 将字符串的字符全部转换为小写字符 function lowerCase(str) { let ...

  • powershell运行脚本禁止错误

    我们如果换了新电脑,然后装上了PowerShell,之前有很多实用的nodejs小工具,还想在新电脑上使用,但是发现在powershell中都一直报错..该怎么处理?跟俺来! 无法加载文件 **.ps ...

  • 嵌入式链接脚本(Link Script)介绍

    嵌入式链接脚本(Link Script)介绍

  • Miss.Penelope花哨脚本字体

    给大家带来的是Miss.Penelope花哨脚本字体 ,在办公学习,平面设计,标题海报的时候都能用得到.每一款字体都具有自己独特的风格,既具有艺术性,又符合字体构形规律,适合各类标题及广告设计.本站提 ...

  • PDF Squeezer 3 for Mac(PDF简易压缩工具)

    pdf压缩软件哪个好?试试macw带来的PDF简易压缩工具PDF Squeezer for Mac吧!在PDF文件过大的时候,PDF Squeezer 可以移除不相干的信息和压缩图像的方式来缩减PDF ...

  • Pabku优雅简单脚本字体 for mac​

    Pabku Script字体是一种简单而优雅的字体,具有OpenType功能,例如样式替代,初始和最终形式以及连字.该字体看起来优雅,经典,可读,时尚,醒目且易于使用.Pabku脚本字体是摄影,签名或 ...

  • 流畅清新的Mag Nerocy脚本字体

    给大家带来的是流畅清新的Mag Nerocy脚本字体 ,在办公学习,平面设计,标题海报的时候都能用得到.每一款字体都具有自己独特的风格,既具有艺术性,又符合字体构形规律,适合各类标题及广告设计.本站提 ...

  • 【学习笔记】学生脚本写到夜里十一点,除了方向键还有其他操作?

    [学习笔记]学生脚本写到夜里十一点,除了方向键还有其他操作? 最近同学们提交了许多的作品,原来的只是导入一些角色,这几天作品也逐渐成型了.总体感觉来看,还是创意满满,但还是有一些问题. 学生在积木脚本 ...

  • PDF Squeezer 3 for Mac(PDF简易压缩工具) v3.12.1中文激活版

    pdf压缩软件哪个好?试试macw带来的PDF简易压缩工具PDF Squeezer for Mac吧!在PDF文件过大的时候,PDF Squeezer 可以移除不相干的信息和压缩图像的方式来缩减PDF ...

  • 后处理TCL脚本语言:命令、脚本文件、值

    一.UG/Post的开发方法与使用的工具: UG/Post的开发,其核心是TCL语言的运用.TCL是Tool Command Language的缩写,英文发音为tickle,中文名叫工具命令语言,是一 ...