使用mage.exe重新启动Clickonce清单

时间:2014-07-28 20:46:20

标签: clickonce mage

简单的场景。 Visual Studio在发布过程中生成并签名事物。

然后我转到MyApp.dll.config.deploy文件并更新一些设置。

现在,当我尝试安装应用程序时,clickonce安装程序说MyApp.dll.config的计算哈希值与清单中指定的哈希值不同。

所以我尝试了mage.exe -sign MyApplicationManifest -certfile ... -password ...,结果是MyApp.vsto successfully signed

但我仍然收到计算的哈希错误。我错过了一步吗?

我已阅读以下链接: http://msdn.microsoft.com/en-us/library/acz3y3te(v=vs.110).aspx http://blogs.msdn.com/b/msiclickonce/archive/2009/07/17/clickonce-application-fails-after-changing-configuration-file-and-resigning-the-manifest.aspx

3 个答案:

答案 0 :(得分:9)

要清除清单,需要执行几个步骤。我使用powershell自动完成它。

  1. 如果发布步骤重命名了.deploy扩展名的文件,则需要将其重命名为原始文件。
  2. 在您的应用程序清单上运行mage.exe -update。如果您有部署清单,则需要执行相同的操作,但在mage.exe的命令行参数中,您需要指定应用程序清单的相对位置。
  3. 将所有文件重命名为.deploy扩展名(除清单之外的所有文件。
  4. 如果您有setup.exe引导程序并且您更改了它(例如更新网址),则必须使用delcert.exe删除签名,然后使用signtool.exe
  5. 对其进行签名

    注意:一旦exe签名,除非您使用delcert.exe删除签名,否则无法使用signtool重新签名。

    如果您的clickonce是VSTO clickonce,您的清单名称将不同(我认为MyApp.dll.manifest vs MyApp.exe.manifest)。

    更新我已将powershell脚本包含在已修改的敏感信息中。

    $root = "$PSScriptRoot"
    $ToolsPath = "C:\Tools"
    $CertFile = $ToolsPath + "\REMOVED"
    $CertPassword = "REMOVED"
    
    #Update the setup.exe bootstrappers update url
    Start-Process "$PSScriptRoot\setup.exe" -ArgumentList "-url=`"$ClickOnceUpdateUrl`"" -Wait
    
    #The bootstrappers signature is now invalid since we updated the url
    #We need to remove the old signature
    Start-Process 'C:\Tools\delcert.exe' -ArgumentList "`"$root\setup.exe`"" -Wait
    
    Write-Host "$root [writeline]"
    #Resign with signtool
    Invoke-Expression 'C:\Tools\signtool.exe sign /d "PUBLISHER NAME" /f "$CertFile" /p "$CertPassword" "$root\setup.exe"'
    
    #update config properties
    $CodeBasePath = Convert-Path "$PSScriptRoot\Application Files\MYAPP_*"
    $ConfigPath = $CodeBasePath + "\MYAPP.dll.config.deploy"
    [xml] $xml = Get-Content $ConfigPath
    
    $ApiEndpoint = $xml.SelectSingleNode('/configuration/appSettings/add[@key="MYAPP:ApiBaseUrl"]')
    $ApiEndpoint.value = $MYAPPApiEndpoint
    $xml.Save($ConfigPath)  
    
    #Begin Resigning various Manifests
    $AppManifestPath = Convert-Path "Application Files\MYAPP_*\MYAPP.dll.manifest"
    
    #Need to resign the application manifest, but before we do we need to rename all the files back to their original names (remove .deploy)
    Get-ChildItem "$CodeBasePath\*.deploy" -Recurse | Rename-Item -NewName { $_.Name -replace '\.deploy','' }
    
    #Resign application manifest
    Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MYAPP.dll.manifest" -certFile "$CertFile" -password "$CertPassword" -if "ID.ico"'
    
    #Regisn deployment manifests in root and versioned folder
    Invoke-Expression 'C:\Tools\mage.exe -update "$CodeBasePath\MYAPP.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "PUBLISHER NAME" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'
    Invoke-Expression 'C:\Tools\mage.exe -update "$root\MYAPP.vsto" -certFile "$CertFile" -password "$CertPassword" -appManifest "$AppManifestPath" -pub "PUBLISHER NAME" -ti "http://timestamp.globalsign.com/scripts/timstamp.dll"'
    
    #Rename files back to the .deploy extension, skipping the files that shouldn't be renamed
    Get-ChildItem -Path "Application Files\*"  -Recurse | Where-Object {!$_.PSIsContainer -and $_.Name -notlike "*.manifest" -and $_.Name -notlike "*.vsto"} | Rename-Item -NewName {$_.Name + ".deploy"}
    

答案 1 :(得分:2)

本文详细介绍了相同的案例: https://blogs.msdn.microsoft.com/vsto/2009/04/29/signing-and-re-signing-manifests-in-clickonce-saurabh-bhatia/

总结的解决方案是:

  • 修改配置文件
  • 如果需要,删除文件的.deploy扩展名
  • 更新应用程序清单

    mage.exe -update Myapplication.exe.manifest -certfile mycert.pfx

  • 更新.application文件

    mage.exe -update Myapplication.application -appmanifest“Application Files \ MyApplication_%Version%\ Myapplication.exe.manifest”-certfile mycert.pfx

  • 如果需要,还原.deploy扩展

答案 2 :(得分:1)

您可能对我编写的PowerShell脚本感兴趣,并且对清单进行签名也修复了新版本的ClickOnce不支持SHA256证书和SmartScreen不再支持SHA1,从而导致一个或另一个发出警告。

SignClickOnceApp.ps1

发布时的代码

<#
.SYNOPSIS 
    A PowerShell Script to correctly sign a ClickOnce Application.
.DESCRIPTION 
    Microsoft ClickOnce Applications Signed with a SHA256 Certificate show as Unknown Publisher during installation, ClickOnce Applications signed with a SHA1 Certificate show an Unknown Publisher SmartScreen Warning once installed, this happens because:
    1) The ClickOnce installer only supports SHA1 certificates (not SHA256), but,
    2) Microsoft has depreciated SHA1 for Authenticode Signing.

    This script uses two code signing certificates (one SHA1 and one SHA256) to sign the various parts of the ClickOnce Application so that both the ClickOnce Installer and SmartScreen are happy.
.PARAMETER VSRoot
    The Visual Studio Projects folder, if not provided .\Documents\Visual Studio 2015\Projects will be assumed
.PARAMETER SolutionName
    The Name of the Visual Studio Solution (Folder), if not provided the user is prompted.
.PARAMETER ProjectName
    The Name of the Visual Studio Project (Folder), if not provided the user is prompted.
.PARAMETER SHA1CertThumbprint
    The Thumbprint of the SHA1 Code Signing Certificate, if not provided the user is prompted.
.PARAMETER SHA256CertThumbprint
    The Thumbprint of the SHA256 Code Signing Certificate, if not provided the user is prompted.
.PARAMETER TimeStampingServer
    The Time Stamping Server to be used while signing, if not provided the user is prompted.
.PARAMETER PublisherName
    The Publisher to be set on the ClickOnce files, if not provided the user is prompted.
.PARAMETER Verbose
    Writes verbose output.
.EXAMPLE
    SignClickOnceApp.ps1 -VSRoot "C:\Users\Username\Documents\Visual Studio 2015\Projects" -SolutionName "MySolution" -ProjectName "MyProject" -SHA1CertThumbprint "f3f33ccc36ffffe5baba632d76e73177206143eb" -SHA256CertThumbprint "5d81f6a4e1fb468a3b97aeb3601a467cdd5e3266" -TimeStampingServer "http://time.certum.pl/" -PublisherName "Awesome Software Inc."
    Signs MyProject in MySolution which is in C:\Users\Username\Documents\Visual Studio 2015\Projects using the specified certificates, with a publisher of "Awesome Software Inc." and the Certum Timestamping Server.
.NOTES 
    Author  : Joe Pitt
    License : SignClickOnceApp by Joe Pitt is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
.LINK 
    https://www.joepitt.co.uk/Project/SignClickOnceApp/
#>
param (
    [string]$VSRoot, 
    [string]$SolutionName, 
    [string]$ProjectName, 
    [string]$SHA1CertThumbprint, 
    [string]$SHA256CertThumbprint, 
    [string]$TimeStampingServer,
    [string]$PublisherName,
    [switch]$Verbose
)

$oldverbose = $VerbosePreference
if($Verbose) 
{
    $VerbosePreference = "continue" 
}

# Visual Studio Root Path
if(!$PSBoundParameters.ContainsKey('VSRoot'))
{
    $VSRoot = '.\Documents\Visual Studio 2015\Projects\'
}
if (Test-Path "$VSRoot")
{
    Write-Verbose "Using '$VSRoot' for Visual Studio Root"
}
else
{
    Write-Error -Message "VSRoot does not exist." -RecommendedAction "Check path and try again" -ErrorId "1" `
        -Category ObjectNotFound -CategoryActivity "Testing VSRoot Path" -CategoryReason "The VSRoot path was not found" `
        -CategoryTargetName "$VSRoot" -CategoryTargetType "Directory"
    exit 1
}

# Solution Path
if(!$PSBoundParameters.ContainsKey('SolutionName'))
{
    $SolutionName = Read-Host "Solution Name"
}
if (Test-Path "$VSRoot\$SolutionName")
{
    Write-Verbose "Using '$VSRoot\$SolutionName' for Solution Path"
    $SolutionPath = "$VSRoot\$SolutionName"
}
else
{
    Write-Error -Message "Solution does not exist." -RecommendedAction "Check Solution Name and try again" -ErrorId "2" `
        -Category ObjectNotFound -CategoryActivity "Testing Solution Path" -CategoryReason "The Solution path was not found" `
        -CategoryTargetName "$VSRoot\$SolutionName" -CategoryTargetType "Directory"
    exit 2
}

# Project Path
if(!$PSBoundParameters.ContainsKey('ProjectName'))
{
    $ProjectName = Read-Host "Project Name"
}
if (Test-Path "$SolutionPath\$ProjectName")
{
    Write-Verbose "Using '$SolutionPath\$ProjectName' for Project Path"
    $ProjectPath = "$SolutionPath\$ProjectName"
}
else
{
    Write-Error -Message "Project does not exist." -RecommendedAction "Check Project Name and try again" -ErrorId "3" `
        -Category ObjectNotFound -CategoryActivity "Testing Project Path" -CategoryReason "The Project path was not found" `
        -CategoryTargetName "$SolutionPath\$ProjectName" -CategoryTargetType "Directory"
    exit 3
}

# Publish Path
if (Test-Path "$ProjectPath\publish")
{
    Write-Verbose "Using '$ProjectPath\publish' for Publish Path"
    $PublishPath = "$ProjectPath\publish"
}
else
{
    Write-Error -Message "Publish path does not exist." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "4" `
        -Category ObjectNotFound -CategoryActivity "Testing Publish Path" -CategoryReason "The publish path was not found" `
        -CategoryTargetName "$ProjectPath\publish" -CategoryTargetType "Directory"
    exit 4
}

# Application Files Path
if (Test-Path "$PublishPath\Application Files")
{
    Write-Verbose "Using '$PublishPath\Application Files' for Application Files Path"
    $AppFilesPath = "$PublishPath\Application Files"
}
else
{
    Write-Error -Message "Application Files path does not exist." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "5" `
        -Category ObjectNotFound -CategoryActivity "Testing Application Files Path" -CategoryReason "The Application Files path was not found" `
        -CategoryTargetName "$PublishPath\Application Files" -CategoryTargetType "Directory"
    exit 5
}

# Target Path
$TargetPath = Convert-Path "$AppFilesPath\${ProjectName}_*"
if ($($TargetPath.Length) -ne 0)
{
    Write-Verbose "Using $TargetPath for Target Path"
}
else
{
    Write-Error -Message "No versions." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "6" `
        -Category ObjectNotFound -CategoryActivity "Searching for published version path" -CategoryReason "No Application has been published using ClickOnce" `
        -CategoryTargetName "$AppFilesPath\${ProjectName}_*" -CategoryTargetType "Directory"
    exit 6
}

# SHA1 Certificate
if(!$PSBoundParameters.ContainsKey('SHA1CertThumbprint'))
{
    $SHA1CertThumbprint = Read-Host "SHA1 Certificate Thumbprint"
}
if ("$SHA1CertThumbprint" -notmatch "^[0-9A-Fa-f]{40}$")
{
    Write-Error -Message "SHA1 Thumbprint Malformed" -RecommendedAction "Check the thumbprint and try again" -ErrorId "7" `
        -Category InvalidArgument -CategoryActivity "Verifying Thumbprint Format" -CategoryReason "Thumbprint is not a 40 character Base64 string" `
        -CategoryTargetName "$SHA1CertThumbprint" -CategoryTargetType "Base64String"
    exit 7
}
$SHA1Found = Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Thumbprint -eq "$SHA1CertThumbprint"} | Measure-Object
if ($SHA1Found.Count -eq 0)
{
    Write-Error -Message "SHA1 Certificate Not Found" -RecommendedAction "Check the thumbprint and try again" -ErrorId "8" `
        -Category ObjectNotFound -CategoryActivity "Searching for certificate" -CategoryReason "Certificate with Thumbprint not found" `
        -CategoryTargetName "$SHA1CertThumbprint" -CategoryTargetType "Base64String"
    exit 8
}

# SHA256 Certificate
if(!$PSBoundParameters.ContainsKey('SHA256CertThumbprint'))
{
    $SHA256CertThumbprint = Read-Host "SHA256 Certificate Thumbprint"
}
if ("$SHA256CertThumbprint" -notmatch "^[0-9A-Fa-f]{40}$")
{
    Write-Error -Message "SHA256 Thumbprint Malformed" -RecommendedAction "Check the thumbprint and try again" -ErrorId "9" `
        -Category InvalidArgument -CategoryActivity "Verifying Thumbprint Format" -CategoryReason "Thumbprint is not a 40 character Base64 string" `
        -CategoryTargetName "$SHA256CertThumbprint" -CategoryTargetType "Base64String"
    exit 9
}
$SHA256Found = Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Thumbprint -eq "$SHA256CertThumbprint"} | Measure-Object
if ($SHA256Found.Count -eq 0)
{
    Write-Error -Message "SHA256 Certificate Not Found" -RecommendedAction "Check the thumbprint and try again" -ErrorId "10" `
        -Category ObjectNotFound -CategoryActivity "Searching for certificate" -CategoryReason "Certificate with Thumbprint not found" `
        -CategoryTargetName "$SHA256CertThumbprint" -CategoryTargetType "Base64String"
    exit 10
}

# TimeStamping Server
if(!$PSBoundParameters.ContainsKey('TimeStampingServer'))
{
    $TimeStampingServer = Read-Host "TimeStamping Server URL"
}
if ("$TimeStampingServer" -notmatch "^http(s)?:\/\/[A-Za-z0-9-._~:/?#[\]@!$&'()*+,;=]+$")
{
    Write-Error -Message "SHA256 Thumbprint Malformed" -RecommendedAction "Check the TimeStamp URL and try again" -ErrorId "11" `
        -Category InvalidArgument -CategoryActivity "Verifying TimeStamping URL" -CategoryReason "TimeStamping URL is not a RFC Compliant URL" `
        -CategoryTargetName "$TimeStampingServer" -CategoryTargetType "URL"
    exit 11
}

# Publisher Name
# Project Path
if(!$PSBoundParameters.ContainsKey('PublisherName'))
{
    $PublisherName = Read-Host "Publisher Name"
}

# Sign setup.exe and application.exe with SHA256 Cert
Write-Verbose "Signing '$PublishPath\Setup.exe' (SHA256)"
Start-Process "$PSScriptRoot\signtool.exe" -ArgumentList "sign /fd SHA256 /td SHA256 /tr $TimeStampingServer /sha1 $SHA256CertThumbprint `"$PublishPath\Setup.exe`"" -Wait -NoNewWindow
Write-Verbose "Signing '$TargetPath\$ProjectName.exe.deploy' (SHA256)"
Start-Process "$PSScriptRoot\signtool.exe" -ArgumentList "sign /fd SHA256 /td SHA256 /tr $TimeStampingServer /sha1 $SHA256CertThumbprint `"$TargetPath\$ProjectName.exe.deploy`"" -Wait -NoNewWindow

# Remove .deploy extensions
Write-Verbose "Removing .deploy extensions"
Get-ChildItem "$TargetPath\*.deploy" -Recurse | Rename-Item -NewName { $_.Name -replace '\.deploy','' } 

# Sign Manifest with SHA256 Cert
Write-Verbose "Signing '$TargetPath\$ProjectName.exe.manifest' (SHA256)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$TargetPath\$ProjectName.exe.manifest`" -ch $SHA256CertThumbprint -if `"Logo.ico`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow

# Sign ClickOnces with SHA1 Cert
Write-Verbose "Signing '$TargetPath\$ProjectName.application' (SHA1)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$TargetPath\$ProjectName.application`"  -ch $SHA1CertThumbprint -appManifest `"$TargetPath\$ProjectName.exe.manifest`" -pub `"$PublisherName`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow
Write-Verbose "Signing '$PublishPath\$ProjectName.application' (SHA1)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$PublishPath\$ProjectName.application`" -ch $SHA1CertThumbprint -appManifest `"$TargetPath\$ProjectName.exe.manifest`" -pub `"$PublisherName`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow

# Readd .deply extensions
Write-Verbose "Re-adding .deploy extensions"
Get-ChildItem -Path "$TargetPath\*"  -Recurse | Where-Object {!$_.PSIsContainer -and $_.Name -notlike "*.manifest" -and $_.Name -notlike "*.application"} | Rename-Item -NewName {$_.Name + ".deploy"}