我需要创建post build事件来执行以下操作:
sn -i MyKey.pfx MyKeyContainerName
tlbimp $(ConfigurationName)\MyCom.tlb /out:$(ConfigurationName)\NETMyCom.dll /keycontainer:MyKeyContainerName
sn -d MyKeyContainerName
当Visual Studio执行第一个语句时,它需要一个密码并等待用户指定并失败。
Microsoft(R).NET Framework Strong 名称实用程序版本2.0.50727.42 版权所有(c)Microsoft Corporation。 保留所有权利。
输入PKCS#12密钥的密码 file:无法解析PKCS#12 在mykey.pfx中的blob - 句柄是 无效。
我尝试使用sn命令行参数指定密码,但我看不到这样做的方法。
请帮忙。
此致 希尔米。
答案 0 :(得分:3)
如果像我一样,你没有使用TFS或MSBUILD来构建,那么至少还有其他两种方式:
a)从脚本运行sn.exe,并将密码写入stdin
请参阅here了解C#示例:
注意:使用.NET4版本的sn.exe似乎不可能将其作为外部进程执行(至少在Windows XP上),并将密码写入stdin(我尝试使用python +和C#,以及sn .exe似乎只是退出而不等待密码输入)。
b)使用sn.exe重新签名,使用已安装的pfx。
如果您已经安装了pfx文件,那么您可能知道容器名称(通常Visual Studio使用类似VS_KEY_ABAB1234ABAB1234的名称)
如果像我一样,您不知道或记住容器名称,那么只需重新安装pfx文件:
sn -i myPfxFile VS_KEY_ABAB1234ABAB1234
sn.exe会提示您输入密码,因为它会在pfx文件中安装证书。
然后,您可以让sn.exe重新签名您的程序集,而不提示输入密码:
sn -Rca myAssembly.dll myVSkey
以上内容可以在构建脚本中使用,因为不需要交互: - )
请记住检查签名是否确实有效:
sn -v myAssembly.dll
答案 1 :(得分:2)
我今天遇到了这个问题,我在ClickOnce C#app中使用了一个C ++ DLL。
我在DLL上设置延迟签名,然后使用post-build事件来运行SN,如下所示:
ECHO <your-password-here> | sn.exe -R $(OutDir)$(TargetFileName) $(MSBuildProjectDirectory)<path-to-pfx-file>
喜欢老式批处理方法。 ECHO <your-password-here>
打印您的密码,管道|
管道输出到SN.exe,后者取密码。
你可能不会像我一样需要延迟签名和-R开关,但你明白了。
编辑:这可能没有更长时间的工作 - 我的回答是从2013年开始的,当时我正在使用VS2010。答案 2 :(得分:2)
我已经研究了近两天了。我尝试了旧版本的sn.exe(可以追溯到2.0!),但我无法使用echo PASSWORD |
技巧。
最后,我这样做了:
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
Start-Process "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sn.exe " -ArgumentList "-i
`"Certificate.pfx`" VS_KEY_XXXXXXX" -NoNewWindow -Wait
[System.Windows.Forms.SendKeys]::SendWait("PASSWORDHERE~")
SendWait()
将密钥发送到当前激活的窗口。由于我们使用sn.exe
使用Start-Process
修饰符启动-NoNewWindow
,因此我们的窗口已经过关注。~
是一个特殊字符,表示ENTER按钮。 {ENTER}
也是可能的,但这是键盘上小键盘附近的Enter按钮。可能没有任何区别,但我只是想确定。我使用的来源:https://technet.microsoft.com/en-us/library/ff731008.aspx
由于AppActivate()
,我最终不需要Visual Basic的-NoNewWindow
。
更新:使用-Wait
参数更好地工作!
答案 3 :(得分:1)
不幸的是,这里没有提到的方法对我有用。我必须在Docker容器中注册几个pfxs。
所以我重新开发了
使用RSACryptoServiceProvider在C#中使用sn.exe -i <infile> <container>
命令。源和应用程序在GitHub中,位于
SnInstallPfx项目。
SnInstallPfx应用程序接受PFX密钥及其密码。它会自动计算密钥容器名称(VS_KEY _ *)(从MSBuild源代码中借用)并将其安装到强名称CSP。
用法:
SnInstallPfx.exe <pfx_infile> <pfx_password>
答案 4 :(得分:0)
我需要将其自动化并发现此问题。在回答this之后(非常感谢@Thomas Rijsewijk),我编写了该版本:
# Start the sn.exe process
Start-Process $SnExePath -ArgumentList "-i $PfxCertificatePath $LocalContainerName" -NoNewWindow
# Wait for the process to start
Start-Sleep 2
# This workaround allows to forward the password to the standard input
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[System.Windows.Forms.SendKeys]::SendWait("$($PfxCertificatePassword){ENTER}")
Start-Sleep 2
# This ENTER is to return from the sn.exe process
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
使用-Wait
命令的Start-process
开关不能解决问题,因为PS脚本在将密码转发给它之前正在等待sn.exe进程终止。
答案 5 :(得分:0)
经过长时间的研究,我可以为各种自动化环境(例如Azure devops)运行sn.ex。我的代码在这里:
Start-Process cmd.exe
Sleep 3
$WshShell = New-Object -ComObject WScript.Shell
Sleep 3
$WshShell.sendkeys(".\sn.exe -i $PfxCertificatePath VS_KEY_10D1C85C6387479B{Enter}");
Sleep 3;
$WshShell.sendkeys("**password**{Enter}");
Sleep 3;
$WshShell.sendkeys("{Enter}");
答案 6 :(得分:0)
使用WinAPI,我们可以这样做:
Param(
[string] $Password ,
[string] $CertFilePath
)
function ExecuteCommand([string]$message)
{
try{
foreach ($char in [char[]]$message) {
[void][WPIA.ConsoleUtils]::PostMessage($handle, [WPIA.ConsoleUtils]::WM_CHAR, [int]$char, 0)
}
[void][WPIA.ConsoleUtils]::PostMessage($handle, [WPIA.ConsoleUtils]::WM_CHAR, 13, 0)
Sleep 3
}catch{
}
}
Add-Type -Name ConsoleUtils -Namespace WPIA -MemberDefinition @'
[DllImport("user32.dll")]
public static extern int PostMessage(int hWnd, uint Msg, int wParam, int lParam);
public const int WM_CHAR = 0x0102;
'@
$Win32API = Add-Type -Name Funcs -Namespace Win32 -PassThru -MemberDefinition @'
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, IntPtr lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(IntPtr lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
'@
[System.Reflection.Assembly]::Load("Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[System.Reflection.Assembly]::Load("Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
$publish = New-Object Microsoft.Build.Tasks.ResolveKeySource
$publish.KeyFile=$CertFilePath
try
{
$publish.Execute()
}
catch{
$VsKey= [regex]::match($error[0].Exception,'VS_KEY_[A-F0-9]+').Value
$VsKey =$VsKey -replace "`n|`r"
}
$quotedCertPath='"'+$CertFilePath+'"'
Write-Host 'VsKey='$VsKey
start cmd
$proc=Get-Process | Where-Object {$_.Name -like "*cmd*"}
$proc.MainWindowTitle
$handle = $proc.MainWindowHandle
'1:'+$handle
if($handle -eq 0){
$handle=$Win32API::FindWindow([IntPtr]::Zero, 'C:\WINDOWS\system32\cmd.exe')
'2:'+$handle
}
if($handle -eq 0){
$handle=$Win32API::FindWindow('C:\WINDOWS\system32\cmd.exe', [IntPtr]::Zero)
'3:'+$handle
}
if($handle -eq 0){
$proc2 = Start-Process cmd.exe -PassThru
$proc2
Get-Process -id $proc2.Id
Sleep 3;
$handle = (Get-Process -id $proc2.Id).MainWindowHandle
$handle
$proc.MainWindowHandle
$proc.Refresh()
Sleep 3;
$proc.MainWindowHandle
}
Write-Host 'Handle='$handle
ExecuteCommand 'echo Starting > d:\a\1\s\Authenticode\log.txt'
$SnCommand=[string]::Format(".\sn.exe -i {0} {1}",$quotedCertPath,$VsKey)
ExecuteCommand $SnCommand
ExecuteCommand $Password