Skip to content

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:

  1. Is the Adobe file there? If not, there's nothing to do.
  2. Is the node.exe in 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.
  3. Does the installed node.exe need 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.
  4. Is the installed node.exe 64-bit? The package has only the x64 build. Thus, the script skips ARM computers.

If everything is fine, it:

  1. Renames the old node.exe to node-<old version>-<date and time>.replaced. Windows allows this even while Creative Cloud is using the file, so the user is not interrupted.
  2. Copies the new node.exe in and checks that it matches the package file exactly.
  3. 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.

  1. 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.exe
    • https://nodejs.org/dist/v24.22.1/win-x64/node.exe
  2. Save each file in a different folder, because both files have the name node.exe.
  3. Check each file before you upload it. In PowerShell, in the folder with the file:
    (Get-Item .\node.exe).VersionInfo.FileVersion
    (Get-FileHash .\node.exe -Algorithm SHA256).Hash
    (Get-AuthenticodeSignature .\node.exe).SignerCertificate.Subject
    
  4. Compare the hash with the win-x64/node.exe line in SHASUMS256.txt for that version, for example https://nodejs.org/dist/v22.23.4/SHASUMS256.txt. The signer should be CN=OpenJS Foundation.

Create the packages

  1. Save the two scripts at the bottom of this page as Update-AdobeCCLibrariesNode.ps1 and Update-AdobeCCXNode.ps1.
  2. In the Tanium Console, go to Administration > Content > Packages.
  3. 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
  1. 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

  1. In Interact, see which computers have the old files:
    Get Computer Name and File Version[C:\Program Files\Common Files\Adobe\Creative Cloud Libraries\libs\node.exe] and File Version[C:\Program Files\Adobe\Adobe Creative Cloud Experience\libs\node.exe] from all machines with Installed Applications:Name contains "Adobe Creative Cloud"
    
  2. Select the computers, click Deploy Action, and choose one of the two packages. Deploy the other package the same way.
  3. Click Show preview to continue, then Deploy Action.
  4. 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 .replaced file in the folder is normal until the next restart. Scanners look for files named node.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