Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auto-update powershell script requires reset of PSModulePath #8051

Merged
merged 13 commits into from Feb 17, 2024
5 changes: 5 additions & 0 deletions .changeset/gorgeous-poems-joke.md
@@ -0,0 +1,5 @@
---
"electron-updater": patch
---

fix: auto-update powershell script requires reset of `PSModulePath`
16 changes: 6 additions & 10 deletions .github/workflows/test.yaml
Expand Up @@ -8,14 +8,9 @@ on:

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
inputs:
build-docker-locally:
type: boolean
description: Force rebuild docker images for CI tests
required: false

permissions:
contents: read # to fetch code (actions/checkout)
contents: read

jobs:
test-linux:
Expand Down Expand Up @@ -55,7 +50,7 @@ jobs:
env:
TEST_FILES: ${{ matrix.testFiles }}
FORCE_COLOR: 1

test-mac:
runs-on: macos-latest
steps:
Expand All @@ -73,7 +68,7 @@ jobs:
env:
TEST_FILES: masTest,dmgTest,filesTest,macPackagerTest
FORCE_COLOR: 1

# Need to separate from other tests because logic is specific to when TOKEN env vars are set
test-updater:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -101,8 +96,9 @@ jobs:
strategy:
matrix:
testFiles:
- winCodeSignTest
- installerTest,appxTest,msiTest,portableTest,assistedInstallerTest,protonTest
- BuildTest,oneClickInstallerTest,winCodeSignTest,winPackagerTest,webInstallerTest
- BuildTest,oneClickInstallerTest,winPackagerTest,nsisUpdaterTest,webInstallerTest
steps:
- name: Checkout code repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
Expand All @@ -112,7 +108,7 @@ jobs:
with:
cache-key: v-17.0.0-windows-electron
cache-path: ~\AppData\Local\electron\Cache

- name: Test
run: pnpm ci:test
env:
Expand Down
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -34,8 +34,7 @@
"ci:publish": "pnpm compile && pnpm publish -r && changeset tag",
"jsdoc": "ts2jsdoc packages/builder-util-runtime packages/builder-util packages/app-builder-lib packages/electron-builder packages/electron-publish packages/electron-updater packages/dmg-builder",
"jsdoc2md2html": "node scripts/jsdoc2md2html.js",
"prepare": "husky install",
"update-deps": "pnpm update -i -r --latest"
"prepare": "husky install"
},
"//": "repository must be specified otherwise conventional-changelog will use forked repo (currently cloned)",
"repository": "https://github.com/electron-userland/electron-builder",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/targets/nsis/NsisTarget.ts
Expand Up @@ -397,7 +397,7 @@ export class NsisTarget extends Target {
} else {
await execWine(installerPath, null, [], { env: { __COMPAT_LAYER: "RunAsInvoker" } })
}
await packager.sign(uninstallerPath, " Signing NSIS uninstaller")
await packager.sign(uninstallerPath, "signing NSIS uninstaller")

delete defines.BUILD_UNINSTALLER
// platform-specific path, not wine
Expand Down
Expand Up @@ -25,7 +25,7 @@ export function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap,
let lastOperation: Operation | null = null

// for now only one file is supported in block map
const blockMapFile = newBlockMap.files[0]
const blockMapFile: { name: string; offset: number } = newBlockMap.files[0]
const operations: Array<Operation> = []
const name = blockMapFile.name
const oldEntry = nameToOldBlocks.get(name)
Expand All @@ -41,7 +41,7 @@ export function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap,

let newOffset = blockMapFile.offset
for (let i = 0; i < newFile.checksums.length; newOffset += newFile.sizes[i], i++) {
const blockSize = newFile.sizes[i]
const blockSize: number = newFile.sizes[i]
const checksum = newFile.checksums[i]
let oldOffset = checksumToOldOffset.get(checksum)
if (oldOffset != null && checksumToOldSize.get(checksum) !== blockSize) {
Expand Down
Expand Up @@ -27,11 +27,13 @@ export function verifySignature(publisherNames: Array<string>, unescapedTempUpda
// guaranteed that the path will not contain any illegal characters like <>:"/\|?*
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
const tempUpdateFile = unescapedTempUpdateFile.replace(/'/g, "''")
logger.info(`Verifying signature ${tempUpdateFile}`)

// https://github.com/electron-userland/electron-builder/issues/2421
// https://github.com/electron-userland/electron-builder/issues/2535
// Resetting PSModulePath is necessary https://github.com/electron-userland/electron-builder/issues/7127
execFile(
"chcp 65001 >NUL & powershell.exe",
`set "PSModulePath="; chcp 65001 >NUL & powershell.exe`,
["-NoProfile", "-NonInteractive", "-InputFormat", "None", "-Command", `"Get-AuthenticodeSignature -LiteralPath '${tempUpdateFile}' | ConvertTo-Json -Compress"`],
{
shell: true,
Expand All @@ -44,7 +46,6 @@ export function verifySignature(publisherNames: Array<string>, unescapedTempUpda
resolve(null)
return
}

const data = parseOut(stdout)
if (data.Status === 0) {
const subject = parseDn(data.SignerCertificate.Subject)
Expand Down
33 changes: 33 additions & 0 deletions test/snapshots/updater/nsisUpdaterTest.js.snap
Expand Up @@ -471,6 +471,39 @@ Array [
]
`;

exports[`test custom signature verifier - signing error message 1`] = `"ERR_UPDATER_INVALID_SIGNATURE"`;

exports[`test custom signature verifier - signing error message 2`] = `
Array [
"checking-for-update",
"update-available",
"error",
]
`;

exports[`test custom signature verifier 1`] = `
Object {
"files": Array [
Object {
"sha512": "xrTrW8dzWYlPnu71Y4lpLIAuIurBZJvZmqEZyz1rzM3CbbE1Z+T+P5qYYZgwmhmXdYPOpvnmYKa0HGdgXggwtQ==",
"url": "TestApp-Setup-1.1.0.exe",
},
],
"releaseName": "1.1.0",
"releaseNotes": "",
"tag": "v1.1.0",
"version": "1.1.0",
}
`;

exports[`test custom signature verifier 2`] = `
Array [
"checking-for-update",
"update-available",
"update-downloaded",
]
`;

exports[`test download and install 1`] = `
Object {
"files": Array [
Expand Down
57 changes: 44 additions & 13 deletions test/src/updater/nsisUpdaterTest.ts
Expand Up @@ -186,6 +186,7 @@ test("file url github", async () => {
updater.updateConfigPath = await writeUpdateConfig(options)
updater.signals.updateDownloaded(info => {
expect(info.downloadedFile).not.toBeNull()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
delete (info as any).downloadedFile
expect(info).toMatchSnapshot()
})
Expand All @@ -203,6 +204,7 @@ test("file url github pre-release and fullChangelog", async () => {
updater.updateConfigPath = await writeUpdateConfig(options)
updater.signals.updateDownloaded(info => {
expect(info.downloadedFile).not.toBeNull()
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
delete (info as any).downloadedFile
expect(info).toMatchSnapshot()
})
Expand Down Expand Up @@ -284,10 +286,11 @@ test.ifAll("valid signature using DN", async () => {
repo: "__test_nsis_release",
publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"],
})

await validateDownload(updater)
})

test.skip.ifAll("invalid signature", async () => {
test.ifWindows("invalid signature", async () => {
const updater = await createNsisUpdater("0.0.1")
updater.updateConfigPath = await writeUpdateConfig({
provider: "github",
Expand All @@ -300,6 +303,36 @@ test.skip.ifAll("invalid signature", async () => {
expect(actualEvents).toMatchSnapshot()
})

test.ifWindows("test custom signature verifier", async () => {
const updater = await createNsisUpdater("1.0.2")
updater.updateConfigPath = await writeUpdateConfig<GithubOptions>({
provider: "github",
owner: "develar",
repo: "__test_nsis_release",
publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"],
})
updater.verifyUpdateCodeSignature = (publisherName: string[], path: string) => {
return Promise.resolve(null)
}
await validateDownload(updater)
})

test.ifWindows("test custom signature verifier - signing error message", async () => {
const updater = await createNsisUpdater("1.0.2")
updater.updateConfigPath = await writeUpdateConfig<GithubOptions>({
provider: "github",
owner: "develar",
repo: "__test_nsis_release",
publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"],
})
updater.verifyUpdateCodeSignature = (publisherName: string[], path: string) => {
return Promise.resolve("signature verification failed")
}
const actualEvents = trackEvents(updater)
await assertThat(updater.checkForUpdates().then((it): any => it?.downloadPromise)).throws()
expect(actualEvents).toMatchSnapshot()
})

// disable for now
test("90 staging percentage", async () => {
const userIdFile = path.join(tmpdir(), "electron-updater-test", "userData", ".updaterId")
Expand Down Expand Up @@ -365,21 +398,19 @@ test.ifAll("test download and install", async () => {
})

await validateDownload(updater)

const actualEvents = trackEvents(updater)
expect(actualEvents).toMatchObject([])
// await updater.quitAndInstall(true, false)
})

test.ifAll("test downloaded installer", async () => {
const updater = await createNsisUpdater()
updater.updateConfigPath = await writeUpdateConfig<GenericServerOptions>({
provider: "generic",
url: "https://develar.s3.amazonaws.com/test",
test.skip.ifWindows("test downloaded installer", async () => {
const updater = await createNsisUpdater("1.0.1")
updater.updateConfigPath = await writeUpdateConfig<GithubOptions>({
provider: "github",
owner: "mmaietta",
repo: "electron-builder-test",
})

const actualEvents = trackEvents(updater)

expect(actualEvents).toMatchObject([])
// await updater.quitAndInstall(true, false)
await validateDownload(updater)
// expect(actualEvents).toMatchObject(["checking-for-update", "update-available", "update-downloaded"])
updater.quitAndInstall(true, false)
expect(actualEvents).toMatchObject(["checking-for-update", "update-available", "update-downloaded", "before-quit-for-update"])
})