Update Node.js in Adobe Creative Cloud (Package)
Two Tanium packages that swap the old node.exe files inside Adobe Creative Cloud for the patched builds from nodejs.org, while keeping the same major version.
Creative Cloud ships its own private copies of Node.js. Vulnerability scanners flag them like any other Node.js install. For example, Tanium Comply flags them for CVE-2026-111497 (a Brotli flaw). Installing or updating Node.js from nodejs.org does not touch these copies, because they sit inside Adobe's own folders. Until Adobe ships an update, the only fix is to replace the file.
| Package | File it replaces | Node.js line | Fixed in |
|---|---|---|---|
| Update Node.js in Adobe CC Libraries | C:\Program Files\Common Files\Adobe\Creative Cloud Libraries\libs\node.exe |
22.x | 22.23.4 |
| Update Node.js in Adobe CC Experience | C:\Program Files\Adobe\Adobe Creative Cloud Experience\libs\node.exe |
24.x | 24.22.1 |
Test status
Tested through Tanium on one Windows computer with Creative Cloud open. Both files were replaced, Creative Cloud kept working, and the old file was gone after a restart. A Comply scan after the fix has not been checked yet. See Test results.
Adobe doesn't support this
This changes files inside Adobe's install folders. If a user opens a ticket with Adobe, Adobe may ask them to repair Creative Cloud first. A Creative Cloud update or repair puts Adobe's own node.exe back, which can bring the old version back too.
What the script does
First it checks that the swap is safe. If any check fails, it stops and changes nothing:
- Is the Adobe file there? If not, there's nothing to do.
- Is the
node.exein the package correct? It must be the same major version (22 or 24), at or above the fixed version, and the 64-bit (x64) build. - Does the installed
node.exeneed it? It must be the same major version and older than the fixed version. The script does not touch a newer file. The script never replaces a different major version, because Adobe add-ons work only with the Node.js version that they were built for. - Is the installed
node.exe64-bit? The package has only the x64 build. Thus, the script skips ARM computers.
If everything is fine, it:
- Renames the old
node.exetonode-<old version>-<date and time>.replaced. Windows allows this even while Creative Cloud is using the file, so the user is not interrupted. - Copies the new
node.exein and checks that it matches the package file exactly. - Deletes the old file. If Creative Cloud is still using it, Windows deletes it at the next restart.
If the copy fails, the script renames the old file back so Creative Cloud keeps working. It only closes Adobe's node.exe if the rename fails, and only the copy running from that exact folder.
Every step is written to the Tanium action log. No backup is kept. Repairing or reinstalling Creative Cloud restores Adobe's own file.
The scan clears before the old code stops
The file on disk changes right away, so Tanium and Comply show the new version straight away. A Creative Cloud process that was already running keeps using the old code until Creative Cloud restarts, the user signs out, or the computer restarts. The action log says so when this happens.
Exit codes
The exit code tells you how the action ended. You'll find it at the end of the action log.
| Exit code | Meaning |
|---|---|
0 |
The file was replaced, was already patched, or Creative Cloud isn't installed. |
1 |
The swap failed. The original was put back. Look for [ERROR] in the action log. |
2 |
Skipped. The installed node.exe is a different major version or isn't 64-bit. Nothing was changed. |
3 |
The node.exe in the package is missing, the wrong version, or not 64-bit. Nothing was changed. |
5 |
The script didn't run as SYSTEM or an administrator. Nothing was changed. |
Get node.exe
Each package needs its own node.exe from nodejs.org, the plain Windows x64 file.
- Download the file for each package. Use the version from the table above or a newer one in the same line:
https://nodejs.org/dist/v22.23.4/win-x64/node.exehttps://nodejs.org/dist/v24.22.1/win-x64/node.exe
- Save each file in a different folder, because both files have the name
node.exe. - Check each file before you upload it. In PowerShell, in the folder with the file:
- Compare the hash with the
win-x64/node.exeline inSHASUMS256.txtfor that version, for examplehttps://nodejs.org/dist/v22.23.4/SHASUMS256.txt. The signer should beCN=OpenJS Foundation.
Create the packages
- Save the two scripts at the bottom of this page as
Update-AdobeCCLibrariesNode.ps1andUpdate-AdobeCCXNode.ps1. - In the Tanium Console, go to Administration > Content > Packages.
- Click Create Package and fill in the fields below. Repeat for the second package.
| Field | Value |
|---|---|
| Package Name | <prefix> - Update Node.js in Adobe CC Libraries (Windows) |
| Display Name | <prefix> - Update Node.js in Adobe CC Libraries (Windows) |
| Description | Replaces node.exe in C:\Program Files\Common Files\Adobe\Creative Cloud Libraries\libs with Node.js 22.23.4 x64. Only replaces Node 22.x older than 22.23.4. Exit 0 updated or nothing to do, 1 failed and restored, 2 skipped, 3 bad package file, 5 not admin. |
| Content Set | The content set your team uses for custom content |
| Command | cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File Update-AdobeCCLibrariesNode.ps1 |
| Command Timeout | 10 minutes |
| Download Timeout | 30 minutes. node.exe is about 85 MB |
| Ignore Action Lock | Unchecked |
| Launch this package command in a process group | Checked |
| Files | Click Add File, choose Local File, upload Update-AdobeCCLibrariesNode.ps1. Repeat for the Node.js 22 node.exe. The name must stay exactly node.exe |
| Parameters | None |
| Field | Value |
|---|---|
| Package Name | <prefix> - Update Node.js in Adobe CC Experience (Windows) |
| Display Name | <prefix> - Update Node.js in Adobe CC Experience (Windows) |
| Description | Replaces node.exe in C:\Program Files\Adobe\Adobe Creative Cloud Experience\libs with Node.js 24.22.1 or newer x64. Only replaces Node 24.x older than 24.22.1. Exit 0 updated or nothing to do, 1 failed and restored, 2 skipped, 3 bad package file, 5 not admin. |
| Content Set | The content set your team uses for custom content |
| Command | cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File Update-AdobeCCXNode.ps1 |
| Command Timeout | 10 minutes |
| Download Timeout | 30 minutes. node.exe is about 90 MB |
| Ignore Action Lock | Unchecked |
| Launch this package command in a process group | Checked |
| Files | Click Add File, choose Local File, upload Update-AdobeCCXNode.ps1. Repeat for the Node.js 24 node.exe. The name must stay exactly node.exe |
| Parameters | None |
- Click Save.
Changed the script later?
Tanium keeps its own copy of each uploaded file. After you edit a script, edit the package and upload the file again, or endpoints keep running the old copy.
Deploy
- In Interact, see which computers have the old files:
- Select the computers, click Deploy Action, and choose one of the two packages. Deploy the other package the same way.
- Click Show preview to continue, then Deploy Action.
- To see what happened, go to Administration > Actions > Action History, open the action, select a computer, and read the action log.
Both packages are safe to run on every computer with Creative Cloud. Computers that are already patched, or that don't have the file, end with exit code 0 and nothing changed. Because a Creative Cloud update or repair can bring the old file back, set the action to run again every day under Schedule Deployment on the deploy page.
Example output
Creative Cloud Experience, with Creative Cloud open:
[INFO] Component: Adobe Creative Cloud Experience
[INFO] Target: C:\Program Files\Adobe\Adobe Creative Cloud Experience\libs\node.exe
[INFO] Package node.exe: version 24.22.2, x64
[INFO] Package node.exe signature: Valid, signed by: CN=OpenJS Foundation, O=OpenJS Foundation, L=San Francisco, S=California, C=US
[INFO] Installed node.exe: version 24.19.0, x64
[INFO] Renamed the old node.exe to node-24.19.0-20261106022604.replaced
[INFO] Copied in node.exe 24.22.2 and confirmed the file hash.
[INFO] The old node.exe is still in use by a running Adobe process. It will be deleted at the next restart.
[INFO] 1 node.exe process(es) from this folder are still running the old code (PID 1884). They switch to the new file when Creative Cloud restarts, the user signs out, or the machine restarts.
[INFO] Done. Adobe Creative Cloud Experience node.exe updated from 24.19.0 to 24.22.2.
Creative Cloud Libraries, where nothing was using the old file:
[INFO] Component: Adobe Creative Cloud Libraries
[INFO] Target: C:\Program Files\Common Files\Adobe\Creative Cloud Libraries\libs\node.exe
[INFO] Package node.exe: version 22.23.4, x64
[INFO] Installed node.exe: version 22.23.2, x64
[INFO] Renamed the old node.exe to node-22.23.2-20261106023244.replaced
[INFO] Copied in node.exe 22.23.4 and confirmed the file hash.
[INFO] Deleted the old node.exe.
[INFO] Done. Adobe Creative Cloud Libraries node.exe updated from 22.23.2 to 22.23.4.
Things to know
- A
.replacedfile in the folder is normal until the next restart. Scanners look for files namednode.exe, so they don't count it. The next run of the package also deletes it if nothing is using it. - Adobe may move to a new Node.js major version. When that happens the script skips the computer with exit code
2. Check whether Adobe's new version is already patched before you build a new package. - When Adobe ships a fixed version, stop the schedule. Then Adobe's own updates take over.
Test results
| What was tested | Expected | Result |
|---|---|---|
| Scripts checked for syntax errors | No errors | Pass |
| CC Experience, Creative Cloud open | File replaced, old file set to delete at restart, exit 0 | Pass |
| CC Libraries, nothing using the file | File replaced, old file deleted right away, exit 0 | Pass |
| Restart after the CC Experience run | .replaced file gone |
Pass |
| File Version sensor right after the action | Shows the new version | Pass |
| Creative Cloud after the swap | Keeps working | Pass |
| Comply scan after the fix | CVE no longer lists the Node.js tests | Not tested yet |
| Run again on a patched computer | Exit 0, nothing changed | Not tested yet |
Scripts
######################################################################
# Update Node.js in Adobe CC Libraries (Node 22) - CVE-2026-111497 (Tanium package)
# Files: Update-AdobeCCLibrariesNode.ps1, node.exe (22.23.4 win-x64)
# Exit: 0 updated/nothing to do, 1 failed (original restored),
# 2 skipped, 3 bad package node.exe, 5 not admin
######################################################################
# Restart as 64-bit if the Tanium client started 32-bit PowerShell.
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
$ps64 = Join-Path $env:SystemRoot 'Sysnative\WindowsPowerShell\v1.0\powershell.exe'
& $ps64 -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
$ErrorActionPreference = 'Stop'
# ----- Settings for this package. Edit only this block. -------------
$Component = 'Adobe Creative Cloud Libraries'
$TargetPath = Join-Path $env:CommonProgramFiles 'Adobe\Creative Cloud Libraries\libs\node.exe'
$NodeMajor = 22
$FixedVer = [version]'22.23.4'
# ---------------------------------------------------------------------
$Payload = Join-Path $PSScriptRoot 'node.exe'
$TargetDir = Split-Path -Parent $TargetPath
function Write-Log {
param ([string]$Message, [string]$Level = 'INFO')
Write-Output ("{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message)
}
# File version as Major.Minor.Build (node.exe reports 22.23.2.0, we use 22.23.2).
function Get-NodeVersion {
param ([string]$Path)
$vi = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($Path)
return [version]::new($vi.FileMajorPart, $vi.FileMinorPart, $vi.FileBuildPart)
}
# Reads the PE header to tell x64 from ARM64 or 32-bit. Opens the file with
# full sharing so it also works while node.exe is running.
function Get-PeMachine {
param ([string]$Path)
$share = [System.IO.FileShare]::ReadWrite -bor [System.IO.FileShare]::Delete
$fs = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, $share)
try {
$br = New-Object System.IO.BinaryReader($fs)
if ($br.ReadUInt16() -ne 0x5A4D) { return 'NotPE' }
$fs.Position = 0x3C
$fs.Position = $br.ReadInt32()
if ($br.ReadUInt32() -ne 0x00004550) { return 'NotPE' }
$machine = [int]$br.ReadUInt16()
switch ($machine) {
0x8664 { return 'x64' }
0xAA64 { return 'ARM64' }
0x014C { return 'x86' }
default { return ('Other(0x{0:X4})' -f $machine) }
}
} finally {
$fs.Dispose()
}
}
# Deletes a file at the next restart (used when the old node.exe is still running).
function Remove-AtRestart {
param ([string]$Path)
if (-not ('NodeFix.Native' -as [type])) {
Add-Type -Namespace NodeFix -Name Native -MemberDefinition @'
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
'@
}
# 4 = MOVEFILE_DELAY_UNTIL_REBOOT. PowerShell turns $null into an empty
# string here, which makes the call fail; [NullString]::Value passes a real null.
return [NodeFix.Native]::MoveFileEx($Path, [NullString]::Value, 4)
}
function Get-NodeProcessesInFolder {
@(Get-CimInstance -ClassName Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
Where-Object { $_.ExecutablePath -and ((Split-Path -Parent $_.ExecutablePath) -ieq $TargetDir) })
}
# ----- 0. Must be SYSTEM or admin -----------------------------------
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = ([Security.Principal.WindowsPrincipal]$identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not ($identity.IsSystem -or $isAdmin)) {
Write-Log 'Must run as SYSTEM or an administrator. Nothing changed.' 'ERROR'
exit 5
}
Write-Log "Component: $Component"
Write-Log "Target: $TargetPath"
# ----- 1. Is the Adobe component installed? -------------------------
if (-not (Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
Write-Log 'node.exe not found at the target path. Nothing to do.'
exit 0
}
# Clean up old copies left by an earlier run that were still in use then.
foreach ($leftover in @(Get-ChildItem -LiteralPath $TargetDir -Filter 'node-*.replaced' -File -ErrorAction SilentlyContinue)) {
try {
Remove-Item -LiteralPath $leftover.FullName -Force
Write-Log "Deleted leftover file from an earlier run: $($leftover.Name)"
} catch {
if (Remove-AtRestart $leftover.FullName) {
Write-Log "Leftover file $($leftover.Name) is still in use. It will be deleted at the next restart." 'WARN'
} else {
Write-Log "Leftover file $($leftover.Name) is still in use and could not be set to delete at restart." 'WARN'
}
}
}
# ----- 2. Check the package node.exe --------------------------------
if (-not (Test-Path -LiteralPath $Payload -PathType Leaf)) {
Write-Log "The package node.exe was not found next to the script ($Payload). Nothing changed." 'ERROR'
exit 3
}
$newVer = Get-NodeVersion $Payload
$newArch = Get-PeMachine $Payload
Write-Log "Package node.exe: version $newVer, $newArch"
if ($newVer.Major -ne $NodeMajor -or $newVer -lt $FixedVer -or $newArch -ne 'x64') {
Write-Log "The package node.exe must be Node $NodeMajor.x, $FixedVer or newer, x64. Nothing changed." 'ERROR'
exit 3
}
$sig = Get-AuthenticodeSignature -LiteralPath $Payload
Write-Log ("Package node.exe signature: {0}, signed by: {1}" -f $sig.Status, $(if ($sig.SignerCertificate) { $sig.SignerCertificate.Subject } else { 'none' }))
# ----- 3. Check the installed node.exe ------------------------------
$oldVer = Get-NodeVersion $TargetPath
$oldArch = Get-PeMachine $TargetPath
Write-Log "Installed node.exe: version $oldVer, $oldArch"
if ($oldVer.Major -ne $NodeMajor) {
Write-Log "Installed node.exe is not Node $NodeMajor.x (version $oldVer). This package only handles Node $NodeMajor.x. Nothing changed." 'WARN'
exit 2
}
if ($oldVer -ge $FixedVer) {
Write-Log "Installed node.exe is already $FixedVer or newer. Nothing to do."
exit 0
}
if ($oldArch -ne 'x64') {
Write-Log "Installed node.exe is $oldArch, not x64. This package only carries the x64 build. Nothing changed." 'WARN'
exit 2
}
# ----- 4. Move the old node.exe out of the way ----------------------
$oldName = Join-Path $TargetDir ("node-{0}-{1}.replaced" -f $oldVer, (Get-Date -Format 'yyyyMMddHHmmss'))
$renamed = $false
for ($i = 1; $i -le 5 -and -not $renamed; $i++) {
try {
[System.IO.File]::Move($TargetPath, $oldName)
$renamed = $true
} catch {
Write-Log "Try $i of 5 to rename node.exe failed: $($_.Exception.Message)" 'WARN'
foreach ($p in @(Get-NodeProcessesInFolder | Where-Object { $_.ExecutablePath -ieq $TargetPath })) {
Write-Log "Stopping node.exe PID $($p.ProcessId) (started by PID $($p.ParentProcessId))" 'WARN'
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 2
}
}
if (-not $renamed) {
Write-Log 'Could not move the old node.exe out of the way. Nothing changed.' 'ERROR'
exit 1
}
Write-Log "Renamed the old node.exe to $(Split-Path -Leaf $oldName)"
# ----- 5. Copy in the new node.exe and check it ---------------------
try {
Copy-Item -LiteralPath $Payload -Destination $TargetPath -Force
$nowVer = Get-NodeVersion $TargetPath
$hashMatch = (Get-FileHash -LiteralPath $TargetPath -Algorithm SHA256).Hash -eq (Get-FileHash -LiteralPath $Payload -Algorithm SHA256).Hash
if ($nowVer -ne $newVer -or -not $hashMatch) {
throw "Check after copy failed (version $nowVer, file hash match: $hashMatch)"
}
Write-Log "Copied in node.exe $newVer and confirmed the file hash."
} catch {
Write-Log "Copy failed: $($_.Exception.Message). Putting the original back." 'ERROR'
try {
if (Test-Path -LiteralPath $TargetPath) { Remove-Item -LiteralPath $TargetPath -Force }
[System.IO.File]::Move($oldName, $TargetPath)
Write-Log 'Original node.exe is back in place.'
} catch {
Write-Log "Could not put the original back: $($_.Exception.Message). Rename $oldName to node.exe by hand, or repair Creative Cloud." 'ERROR'
}
exit 1
}
# ----- 6. Delete the old node.exe -----------------------------------
try {
Remove-Item -LiteralPath $oldName -Force
Write-Log 'Deleted the old node.exe.'
} catch {
if (Remove-AtRestart $oldName) {
Write-Log 'The old node.exe is still in use by a running Adobe process. It will be deleted at the next restart.'
} else {
Write-Log "Could not set $oldName to be deleted at restart. The next run of this package will try again." 'WARN'
}
}
$stillRunning = @(Get-NodeProcessesInFolder)
if ($stillRunning.Count -gt 0) {
Write-Log ("{0} node.exe process(es) from this folder are still running the old code (PID {1}). They switch to the new file when Creative Cloud restarts, the user signs out, or the machine restarts." -f $stillRunning.Count, (($stillRunning | ForEach-Object { $_.ProcessId }) -join ', '))
}
Write-Log "Done. $Component node.exe updated from $oldVer to $newVer."
exit 0
######################################################################
# Update Node.js in Adobe CC Experience (Node 24) - CVE-2026-111497 (Tanium package)
# Files: Update-AdobeCCXNode.ps1, node.exe (24.22.1 win-x64)
# Exit: 0 updated/nothing to do, 1 failed (original restored),
# 2 skipped, 3 bad package node.exe, 5 not admin
######################################################################
# Restart as 64-bit if the Tanium client started 32-bit PowerShell.
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
$ps64 = Join-Path $env:SystemRoot 'Sysnative\WindowsPowerShell\v1.0\powershell.exe'
& $ps64 -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $PSCommandPath
exit $LASTEXITCODE
}
$ErrorActionPreference = 'Stop'
# ----- Settings for this package. Edit only this block. -------------
$Component = 'Adobe Creative Cloud Experience'
$TargetPath = Join-Path $env:ProgramFiles 'Adobe\Adobe Creative Cloud Experience\libs\node.exe'
$NodeMajor = 24
$FixedVer = [version]'24.22.1'
# ---------------------------------------------------------------------
$Payload = Join-Path $PSScriptRoot 'node.exe'
$TargetDir = Split-Path -Parent $TargetPath
function Write-Log {
param ([string]$Message, [string]$Level = 'INFO')
Write-Output ("{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message)
}
# File version as Major.Minor.Build (node.exe reports 22.23.2.0, we use 22.23.2).
function Get-NodeVersion {
param ([string]$Path)
$vi = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($Path)
return [version]::new($vi.FileMajorPart, $vi.FileMinorPart, $vi.FileBuildPart)
}
# Reads the PE header to tell x64 from ARM64 or 32-bit. Opens the file with
# full sharing so it also works while node.exe is running.
function Get-PeMachine {
param ([string]$Path)
$share = [System.IO.FileShare]::ReadWrite -bor [System.IO.FileShare]::Delete
$fs = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, $share)
try {
$br = New-Object System.IO.BinaryReader($fs)
if ($br.ReadUInt16() -ne 0x5A4D) { return 'NotPE' }
$fs.Position = 0x3C
$fs.Position = $br.ReadInt32()
if ($br.ReadUInt32() -ne 0x00004550) { return 'NotPE' }
$machine = [int]$br.ReadUInt16()
switch ($machine) {
0x8664 { return 'x64' }
0xAA64 { return 'ARM64' }
0x014C { return 'x86' }
default { return ('Other(0x{0:X4})' -f $machine) }
}
} finally {
$fs.Dispose()
}
}
# Deletes a file at the next restart (used when the old node.exe is still running).
function Remove-AtRestart {
param ([string]$Path)
if (-not ('NodeFix.Native' -as [type])) {
Add-Type -Namespace NodeFix -Name Native -MemberDefinition @'
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
'@
}
# 4 = MOVEFILE_DELAY_UNTIL_REBOOT. PowerShell turns $null into an empty
# string here, which makes the call fail; [NullString]::Value passes a real null.
return [NodeFix.Native]::MoveFileEx($Path, [NullString]::Value, 4)
}
function Get-NodeProcessesInFolder {
@(Get-CimInstance -ClassName Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
Where-Object { $_.ExecutablePath -and ((Split-Path -Parent $_.ExecutablePath) -ieq $TargetDir) })
}
# ----- 0. Must be SYSTEM or admin -----------------------------------
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = ([Security.Principal.WindowsPrincipal]$identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not ($identity.IsSystem -or $isAdmin)) {
Write-Log 'Must run as SYSTEM or an administrator. Nothing changed.' 'ERROR'
exit 5
}
Write-Log "Component: $Component"
Write-Log "Target: $TargetPath"
# ----- 1. Is the Adobe component installed? -------------------------
if (-not (Test-Path -LiteralPath $TargetPath -PathType Leaf)) {
Write-Log 'node.exe not found at the target path. Nothing to do.'
exit 0
}
# Clean up old copies left by an earlier run that were still in use then.
foreach ($leftover in @(Get-ChildItem -LiteralPath $TargetDir -Filter 'node-*.replaced' -File -ErrorAction SilentlyContinue)) {
try {
Remove-Item -LiteralPath $leftover.FullName -Force
Write-Log "Deleted leftover file from an earlier run: $($leftover.Name)"
} catch {
if (Remove-AtRestart $leftover.FullName) {
Write-Log "Leftover file $($leftover.Name) is still in use. It will be deleted at the next restart." 'WARN'
} else {
Write-Log "Leftover file $($leftover.Name) is still in use and could not be set to delete at restart." 'WARN'
}
}
}
# ----- 2. Check the package node.exe --------------------------------
if (-not (Test-Path -LiteralPath $Payload -PathType Leaf)) {
Write-Log "The package node.exe was not found next to the script ($Payload). Nothing changed." 'ERROR'
exit 3
}
$newVer = Get-NodeVersion $Payload
$newArch = Get-PeMachine $Payload
Write-Log "Package node.exe: version $newVer, $newArch"
if ($newVer.Major -ne $NodeMajor -or $newVer -lt $FixedVer -or $newArch -ne 'x64') {
Write-Log "The package node.exe must be Node $NodeMajor.x, $FixedVer or newer, x64. Nothing changed." 'ERROR'
exit 3
}
$sig = Get-AuthenticodeSignature -LiteralPath $Payload
Write-Log ("Package node.exe signature: {0}, signed by: {1}" -f $sig.Status, $(if ($sig.SignerCertificate) { $sig.SignerCertificate.Subject } else { 'none' }))
# ----- 3. Check the installed node.exe ------------------------------
$oldVer = Get-NodeVersion $TargetPath
$oldArch = Get-PeMachine $TargetPath
Write-Log "Installed node.exe: version $oldVer, $oldArch"
if ($oldVer.Major -ne $NodeMajor) {
Write-Log "Installed node.exe is not Node $NodeMajor.x (version $oldVer). This package only handles Node $NodeMajor.x. Nothing changed." 'WARN'
exit 2
}
if ($oldVer -ge $FixedVer) {
Write-Log "Installed node.exe is already $FixedVer or newer. Nothing to do."
exit 0
}
if ($oldArch -ne 'x64') {
Write-Log "Installed node.exe is $oldArch, not x64. This package only carries the x64 build. Nothing changed." 'WARN'
exit 2
}
# ----- 4. Move the old node.exe out of the way ----------------------
$oldName = Join-Path $TargetDir ("node-{0}-{1}.replaced" -f $oldVer, (Get-Date -Format 'yyyyMMddHHmmss'))
$renamed = $false
for ($i = 1; $i -le 5 -and -not $renamed; $i++) {
try {
[System.IO.File]::Move($TargetPath, $oldName)
$renamed = $true
} catch {
Write-Log "Try $i of 5 to rename node.exe failed: $($_.Exception.Message)" 'WARN'
foreach ($p in @(Get-NodeProcessesInFolder | Where-Object { $_.ExecutablePath -ieq $TargetPath })) {
Write-Log "Stopping node.exe PID $($p.ProcessId) (started by PID $($p.ParentProcessId))" 'WARN'
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 2
}
}
if (-not $renamed) {
Write-Log 'Could not move the old node.exe out of the way. Nothing changed.' 'ERROR'
exit 1
}
Write-Log "Renamed the old node.exe to $(Split-Path -Leaf $oldName)"
# ----- 5. Copy in the new node.exe and check it ---------------------
try {
Copy-Item -LiteralPath $Payload -Destination $TargetPath -Force
$nowVer = Get-NodeVersion $TargetPath
$hashMatch = (Get-FileHash -LiteralPath $TargetPath -Algorithm SHA256).Hash -eq (Get-FileHash -LiteralPath $Payload -Algorithm SHA256).Hash
if ($nowVer -ne $newVer -or -not $hashMatch) {
throw "Check after copy failed (version $nowVer, file hash match: $hashMatch)"
}
Write-Log "Copied in node.exe $newVer and confirmed the file hash."
} catch {
Write-Log "Copy failed: $($_.Exception.Message). Putting the original back." 'ERROR'
try {
if (Test-Path -LiteralPath $TargetPath) { Remove-Item -LiteralPath $TargetPath -Force }
[System.IO.File]::Move($oldName, $TargetPath)
Write-Log 'Original node.exe is back in place.'
} catch {
Write-Log "Could not put the original back: $($_.Exception.Message). Rename $oldName to node.exe by hand, or repair Creative Cloud." 'ERROR'
}
exit 1
}
# ----- 6. Delete the old node.exe -----------------------------------
try {
Remove-Item -LiteralPath $oldName -Force
Write-Log 'Deleted the old node.exe.'
} catch {
if (Remove-AtRestart $oldName) {
Write-Log 'The old node.exe is still in use by a running Adobe process. It will be deleted at the next restart.'
} else {
Write-Log "Could not set $oldName to be deleted at restart. The next run of this package will try again." 'WARN'
}
}
$stillRunning = @(Get-NodeProcessesInFolder)
if ($stillRunning.Count -gt 0) {
Write-Log ("{0} node.exe process(es) from this folder are still running the old code (PID {1}). They switch to the new file when Creative Cloud restarts, the user signs out, or the machine restarts." -f $stillRunning.Count, (($stillRunning | ForEach-Object { $_.ProcessId }) -join ', '))
}
Write-Log "Done. $Component node.exe updated from $oldVer to $newVer."
exit 0