Search code examples
clickoncemage

Resign Clickonce manifest using mage.exe


Simple scenario. Visual studio generates and signs things as part of the publish process.

I then go to the MyApp.dll.config.deploy file and update some settings.

Now when I try to install the application the clickonce installer says the MyApp.dll.config has a different computed hash than specified in manifest.

So I tried mage.exe -sign MyApplicationManifest -certfile ... -password ... which results in MyApp.vsto successfully signed

But I still receive the computed hash error. Am I missing a step?

I've read the following links: 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


Solution

  • There's a few steps that need to happen to resign the manifests. I used powershell to do it automatically.

    1. If the publishing step has renamed the files with a .deploy extension you'll need to rename them back to the original.
    2. Run mage.exe -update on your application manifest. If you have a deployment manifest you'll need to do the same, except in the command line argument to mage.exe you need to specify the relative location to the application manifest.
    3. Rename all the files back to .deploy extension (all files except manifests.
    4. If you have a setup.exe bootstrapper and you changed it (such as the update url) you'll have to remove the signature using delcert.exe and then sign it using signtool.exe

    Note: Once an exe has been signed it cannot be resigned with signtool unless you remove the signing with delcert.exe.

    If your clickonce is a VSTO clickonce your manifest names will be different (I think MyApp.dll.manifest vs MyApp.exe.manifest).

    Update I've included the powershell script with sensitive information redacted.

    $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"}