PowerShell 脚本中嵌入二进制文件 7


当你写自动化脚本或者模块时,你可能会经常遇到要引用其它二进制数据。你可能会说“二进制,包含了所有数据”。是的,但是本文主要讨论的不是包含ASCll或者UTF-8编码的普通文本文件。也许有专业术语来描述二级制,但是这里我们直接来举例说明:

  • Word文档
  • 可执行文件
  • 程序集
  • 注册表文件
  • 音频和视频

PowerShell可能需要很多行代码才能实现特定的功能,但是对于一些现成的可执行文件,PowerShell只须几行代码就可以重用这些功能。因此,脚本开发者喜欢直接在外部引用这些文档或者程序集。

在某些情况下,多一个文件依赖,就会增强调用的复杂性和脚本的分发出错几率。怎样将这些依赖的二进制文件嵌入进PowerShell脚本中,让一个脚本文件搞定一切。

这个给出一个思路:先将脚本转换成Base64字符串,然后以变量的形式存储在脚本文件中,在脚本执行时,将字符串还原成二级制文件,然后做正常的工作即可。这样一来只须两个函数Convert-BinaryToString 和 Convert-StringToBinary即可。

function Convert-BinaryToString {
    [CmdletBinding()]
    param (
        [string] $FilePath
    )

    try {
        $ByteArray = [System.IO.File]::ReadAllBytes($FilePath);
    }
    catch {
        throw "Failed to read file. Please ensure that you have permission to the file, and that the file path is correct.";
    }

    if ($ByteArray) {
        $Base64String = [System.Convert]::ToBase64String($ByteArray);
    }
    else {
        throw '$ByteArray is $null.';
    }

    Write-Output -InputObject $Base64String;
}

$Output = Convert-BinaryToString -FilePath C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe;


function Convert-StringToBinary {
    [CmdletBinding()]
    param (
          [string] $InputString
        , [string] $FilePath = ('{0}\{1}' -f $env:TEMP, [System.Guid]::NewGuid().ToString())
    )

    try {
        if ($InputString.Length -ge 1) {
            $ByteArray = [System.Convert]::FromBase64String($InputString);
            [System.IO.File]::WriteAllBytes($FilePath, $ByteArray);
        }
    }
    catch {
        throw ('Failed to create file from Base64 string: {0}' -f $FilePath);
    }

    Write-Output -InputObject (Get-Item -Path $FilePath);
}

$TargetFile = Convert-StringToBinary -InputString $PowerShellExecutable -FilePath C:\temp\powershell.exe;

引用链接:PowerShell: Embed binary data in your script

本文链接: https://www.pstips.net/powershell-embed-binary-data-in-your-script.html
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!

关于 Mooser Lee

我是一个Powershell的爱好者,创建了PowerShell中文博客,热衷于Powershell技术的搜集和分享。本站部分内容来源于互联网,不足之处敬请谅解,并欢迎您批评指正。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

7 条评论 “PowerShell 脚本中嵌入二进制文件

  • mori

    我用你这个代码转换一个exe然后再转换回来大小却不一样了,差了10几个字节大小。我试了很多个程序,直接copy你的代码实验都不行。不知道是为什么啊?

    • Mooser Lee 文章作者

      差的字节,会不会是因为文件名的不同引起的,主要要对比文件本身的内容。
      为了防止网站迁移过程中,出现字符丢失,我刚才又测试了一遍,发现没有问题:

      
      PS> $str=Convert-BinaryToString -FilePath D:\tool\free\fg757p.exe
      PS> Convert-StringToBinary -InputString $str -FilePath D:\tool\free\fg758p.exe
      
          目录: D:\tool\free
      
      
      Mode                LastWriteTime         Length Name                                                 
      ----                -------------         ------ ----                                                 
      -a----        2016/3/23     22:37        2643744 fg758p.exe                                           
      
      
      PS> 
      

      对比文件长度:

      PS> ls D:\tool\free\*.exe
      
      
          目录: D:\tool\free
      
      
      Mode                LastWriteTime         Length Name
      ----                -------------         ------ ----
      -a----       2015/10/25     18:21        2643744 fg757p.exe
      -a----        2016/3/23     22:37        2643744 fg758p.exe
      
      • mori

        那你看下我下面的代码有问题吗?:
        $oriBuf = Get-Content ‘c:\projects\key.exe’
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($oriBuf);
        [System.Convert]::ToBase64String($bytes) | Set-Content ‘c:\projects\key2.b64’

        $b64Buf = Get-Content ‘c:\projects\key2.b64’
        $bb = [System.Convert]::FromBase64String($b64Buf);
        [System.Text.Encoding]::UTF8.GetString($bb) | Set-Content ‘c:\projects\key2.exe’

        我这是第一种方法,key.exe和key2.exe大小差了几十个字节,怎么也没弄明白是怎么回事。另外我还用了下面这段也测试了:
        $memstream = New-Object System.IO.MemoryStream
        [byte[]]$bytes = [System.IO.File]::ReadAllBytes(‘c:\projects\key.exe’)
        $memstream.Write($bytes,0,$bytes.Length)
        [System.Convert]::ToBase64String($memstream.ToArray()) | Set-Content ‘c:\projects\key.b64’

        [byte[]]$readbuf = [System.IO.File]::ReadAllBytes(‘c:\projects\key.b64’)
        [byte[]]$encbuf = [System.Convert]::FromBase64CharArray($readbuf,0,$readbuf.Length)
        $ms = New-Object System.IO.MemoryStream
        $ms.Write($encbuf,0,$encbuf.Length)
        [System.IO.File]::WriteAllBytes(‘c:\projects\key2.exe’,$mem.ToArray())

        key.exe和key2.exe也是差了十几K,我真是不知道问题出在哪了,你的那个代码我也试了,我这个测试的是一个GUI的程序,可能被压缩了,但是这个应该是不影响的啊。
        你的代码我也试了,也是这个问题,我的是powershell v3,win10系统,我换了个exe测试也是这结果,但我觉得这些都不应该影响的啊。求大神解答啊

        • mori

          我看了下,唯一的区别,就是我把base64编码后的内容保存到了文件里,然后再从文件里读出来,解码后再写。是不是我用的函数不对?因为你的是直接编码成字符串了,并没有保存到文件,是不是这里出的问题啊!T_T

          • Mooser Lee 文章作者

            你得先保证字符串你保存的和你读取的是一致。
            这就是问题之所在,不要使用PowerShell中的Set-Content,Set-Content适用于一般日志内容读写。
            如果要高保真的存取字符串,还是需要使用.NET中的方法。
            继续使用我评论中的$str变量:

            PS> [io.file]::WriteAllText("C:\Users\libao\a.txt",$str)
            PS> $r= [io.file]::ReadAllText("C:\Users\libao\a.txt")
            PS> $r.Length
            3524992
            PS> $str.Length
            3524992
            
          • mori

            嗯,这个问题我解决了,用下面的代码:

            #region encode
            $bytesArray = [System.IO.File]::ReadAllBytes(‘d:\projects\mimi.exe’);
            $b64Array = [System.Convert]::ToBase64String($bytesArray);
            [System.IO.File]::WriteAllBytes(‘d:\projects\mimi.b64’, $b64Array.ToCharArray())
            #endregion

            #region decode
            $b64Array2 = [System.IO.File]::ReadAllBytes(‘d:\projects\mimi.b64’);
            [string]$s = [System.Text.Encoding]::ASCII.GetString($b64Array2);
            $bytesArray2 = [System.Convert]::FromBase64String($s);
            [System.IO.File]::WriteAllBytes(‘d:\projects\mimi2.exe’, $bytesArray2);

            #endregion

            这样倒是可以正确还原了,但是问题又来了。因为这样编码的话体积有点太大了,所以我想再转成base64编码之前,线用GzipStream压缩一下,然后再转成base64,这样体积能小很多,但是我在还原的时候发现,虽然还原出的exe文件图标和大小都一致,但是运行的时候却提示我是非法的win32程序,我调试看了下,我在用gzipstream还原后的字节长度比原来少了210个字节,代码我贴在下面,麻烦您帮忙给看下:
            ##############################encrypt#######################################################

            $memstream = New-Object System.IO.MemoryStream
            $gzipstream = New-Object System.IO.Compression.GZipStream($memstream, [System.IO.Compression.CompressionMode]::Compress, $True)
            $bytesArray = [System.IO.File]::ReadAllBytes(‘d:\projects\mimi.exe’);
            $gzipstream.Write($bytesArray, 0, $bytesArray.Length);
            $b64Array = [System.Convert]::ToBase64String($memstream.ToArray());
            [System.IO.File]::WriteAllBytes(‘d:\projects\mimi.b64’, $b64Array.ToCharArray());

            #############################decrypt########################################################
            $ms = New-Object System.IO.MemoryStream
            $b64Array2 = [System.IO.File]::ReadAllBytes(‘d:\projects\mimi.b64’);
            [string]$s = [System.Text.Encoding]::ASCII.GetString($b64Array2);
            $bytesArray2 = [System.Convert]::FromBase64String($s);
            $ms.Write($bytesArray2, 0, $bytesArray2.Length);
            $ms.Position = 0;
            $gs = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress);
            $mem = New-Object System.IO.MemoryStream;
            $buf = New-Object byte[](4096);
            while ($True)
            {
            [int]$intRead = $gs.Read($buf, 0, 4096);
            if ($intRead -eq 0)
            {
            break;
            }
            $mem.Write($buf, 0, $intRead);
            }
            [System.IO.File]::WriteAllBytes(‘d:\projects\mimi2.exe’, $mem.ToArray());

            谢谢!

        • Mooser Lee 文章作者

          哈哈,关于gzip压缩的问题你只需要补充两行代码即可。

          压缩时时:
          在$b64Array = [System.Convert]::ToBase64String($memstream.ToArray());之前
          调用:$gzipstream.Close()

          解压缩时:
          在[System.IO.File]::WriteAllBytes(‘d:\projects\mimi2.exe’, $mem.ToArray());之前
          调用:$mem.Close()