当你写自动化脚本或者模块时,你可能会经常遇到要引用其它二进制数据。你可能会说“二进制,包含了所有数据”。是的,但是本文主要讨论的不是包含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
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!
我用你这个代码转换一个exe然后再转换回来大小却不一样了,差了10几个字节大小。我试了很多个程序,直接copy你的代码实验都不行。不知道是为什么啊?
差的字节,会不会是因为文件名的不同引起的,主要要对比文件本身的内容。
为了防止网站迁移过程中,出现字符丢失,我刚才又测试了一遍,发现没有问题:
对比文件长度:
那你看下我下面的代码有问题吗?:
$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测试也是这结果,但我觉得这些都不应该影响的啊。求大神解答啊
我看了下,唯一的区别,就是我把base64编码后的内容保存到了文件里,然后再从文件里读出来,解码后再写。是不是我用的函数不对?因为你的是直接编码成字符串了,并没有保存到文件,是不是这里出的问题啊!T_T
你得先保证字符串你保存的和你读取的是一致。
这就是问题之所在,不要使用PowerShell中的Set-Content,Set-Content适用于一般日志内容读写。
如果要高保真的存取字符串,还是需要使用.NET中的方法。
继续使用我评论中的$str变量:
嗯,这个问题我解决了,用下面的代码:
#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());
谢谢!
哈哈,关于gzip压缩的问题你只需要补充两行代码即可。
压缩时时:
在$b64Array = [System.Convert]::ToBase64String($memstream.ToArray());之前
调用:$gzipstream.Close()
解压缩时:
在[System.IO.File]::WriteAllBytes(‘d:\projects\mimi2.exe’, $mem.ToArray());之前
调用:$mem.Close()