SmilarScripts / PowerShellSmilar home

WINDOWS / UPDATE

Update VLC
media player.

Inventory installed copies, download the current stable desktop release, update silently and verify the result.

Pilot before rollout

PowerShell parsing and simulated workflow tests passed. Official release metadata and package checksum were checked. Windows installation, user-hive access and Authenticode validation still require a live pilot.

Usage

Inventory only

.\Update-VlcMediaPlayer.ps1 -DetectOnly

Update silently; defer if VLC is open

.\Update-VlcMediaPlayer.ps1

Update silently, allowing active VLC playback to be stopped

.\Update-VlcMediaPlayer.ps1 -CloseRunningVlc

Include another known VLC folder in the inventory

.\Update-VlcMediaPlayer.ps1 -DetectOnly -AdditionalPaths 'D:\Apps\VLC'

Update-VlcMediaPlayer.ps1

Raw

#Requires -Version 5.1<#.SYNOPSISSilent VLC desktop updater for SYSTEM / N-central / NinjaOne. Version 1.0.0..DESCRIPTIONInventories both HKLM registry views, local user hives (including unloadedprofiles), Store/Appx registrations, standard VLC paths and AdditionalPaths.Updates registered machine-wide desktop VLC. If outdated copies exist, theyare consolidated to one current native x86/x64 MSI installation. It downloadsand validates the replacement BEFORE removing anything. No VLC present = no install.Per-user, Store, portable, broken registrations, custom unsafe paths, ARM64,and newer/prerelease versions are reported, not silently declared updated.No Win32_Product, winget dependency, reboot, password or interactive prompts.By default running VLC blocks changes. -CloseRunningVlc stops it (playback ends).Logs/transcript, before/after inventory, summary JSON and verbose MSI logs:%ProgramData%\Smilar\VlcUpdater\<timestamp-guid> (SYSTEM/Administrators only).Official stable release is discovered each run; only reviewed VLC 3.x is applied.A later major release fails for review instead of silently using an older release.Network/proxy and certificate validation must work as SYSTEM. TLS 1.2 required.Exit 0=current/updated/absent or successful detection; 2=unresolved copies;1618=busy/running VLC; 3010=restart required, incomplete; 1460=installer timeout;1=other error. Native MSI failures are propagated. Configure RMM NOT to reboot.Allow 90 minutes for multi-install cleanup. Pilot before fleet deployment.An unfinished removal/install leaves a protected pending.json marker. Review andrecover from its retained installer/logs before clearing it for a fresh run.No exhaustive whole-disk portable-file search; AdditionalPaths are exact folders..EXAMPLE.\Update-VlcMediaPlayer.ps1 -DetectOnly.EXAMPLE.\Update-VlcMediaPlayer.ps1 -CloseRunningVlc#>[CmdletBinding()]param([switch]$DetectOnly, [switch]$CloseRunningVlc, [string[]]$AdditionalPaths = @())$ErrorActionPreference = 'Stop'$script:Findings = [Collections.Generic.List[object]]::new()$script:Problems = [Collections.Generic.List[string]]::new()$script:RunPath = $nullfunction Log([string]$Message) { Write-Host ('[{0}] {1}' -f (Get-Date -Format o), $Message) }function Problem([string]$Message) { $script:Problems.Add($Message); Log "UNRESOLVED: $Message" }function Parse-Version([string]$Text) {    if ($Text -match '^\s*(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?\s*$') {        $revision = if ($Matches[4]) { [int]$Matches[4] } else { 0 }        return [version]::new([int]$Matches[1],[int]$Matches[2],[int]$Matches[3],$revision)    }    return $null}function Latest-Release([string]$Html) {    $versions = @([regex]::Matches($Html, 'href="(\d+\.\d+\.\d+(?:\.\d+)?)/"') |        ForEach-Object { $_.Groups[1].Value } | Sort-Object { Parse-Version $_ } -Descending -Unique)    if (-not $versions.Count) { throw 'No stable numeric release found in official release index.' }    return $versions[0]}function Assert-NoReparse([string]$Path) {    $item = Get-Item -LiteralPath $Path -Force    while ($null -ne $item) {        if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "Reparse path rejected: $Path" }        $item = if ($item -is [IO.DirectoryInfo]) { $item.Parent } else { $item.Directory }    }}function Init-Log {    $root = Join-Path $env:ProgramData 'Smilar\VlcUpdater'    $null = New-Item -ItemType Directory -Path $root -Force    Assert-NoReparse $root    $acl = [Security.AccessControl.DirectorySecurity]::new()    $acl.SetAccessRuleProtection($true,$false)    foreach ($sid in @('S-1-5-18','S-1-5-32-544')) {        $rule = [Security.AccessControl.FileSystemAccessRule]::new(            [Security.Principal.SecurityIdentifier]::new($sid),'FullControl','ContainerInherit,ObjectInherit','None','Allow')        $acl.AddAccessRule($rule)    }    $acl.SetOwner([Security.Principal.SecurityIdentifier]::new('S-1-5-18'))    Set-Acl -LiteralPath $root -AclObject $acl    $script:PendingPath = Join-Path $root 'pending.json'    $script:RunPath = Join-Path $root ((Get-Date -Format 'yyyyMMdd-HHmmss')+'-'+[guid]::NewGuid().ToString('N'))    $null = New-Item -ItemType Directory $script:RunPath    $null = Start-Transcript -Path (Join-Path $script:RunPath 'transcript.log')    Log "Logs: $script:RunPath"}function Add-Record([string]$Scope,[string]$Kind,[string]$Key,[string]$Name,[string]$Version,[string]$Path,[string]$Uninstall,[string]$Publisher) {    $actual = $null    if ($Path -and (Test-Path -LiteralPath (Join-Path $Path 'vlc.exe'))) {        $v = [Diagnostics.FileVersionInfo]::GetVersionInfo((Join-Path $Path 'vlc.exe'))        $actual = '{0}.{1}.{2}.{3}' -f $v.FileMajorPart,$v.FileMinorPart,$v.FileBuildPart,$v.FilePrivatePart    }    $script:Findings.Add([pscustomobject]@{Scope=$Scope;Kind=$Kind;Key=$Key;Name=$Name;Version=$Version;FileVersion=$actual;Path=$Path;Uninstall=$Uninstall;Publisher=$Publisher})}function Read-Uninstall($Base,[string]$Prefix,[string]$Scope) {    $key = $Base.OpenSubKey($Prefix)    if ($null -eq $key) { return }    try {        foreach ($name in $key.GetSubKeyNames()) {            $entry=$key.OpenSubKey($name)            if ($null -eq $entry) { continue }            try {                $display=[string]$entry.GetValue('DisplayName')                if ($display -notmatch '^VLC(?:\s+media\s+player)?(?:\s|$)') { continue }                $path=[string]$entry.GetValue('InstallLocation')                $uninstall=[string]$entry.GetValue('UninstallString')                if (-not $path) {                    $icon=[string]$entry.GetValue('DisplayIcon')                    if ($icon -match '^"?([A-Za-z]:\\.*?\\vlc\.exe)"?(?:,\d+)?$') { $path=Split-Path $Matches[1] }                    elseif ($uninstall -match '^"?([A-Za-z]:\\.*?\\uninstall\.exe)"?\s*$') { $path=Split-Path $Matches[1] }                }                $kind=if ($entry.GetValue('WindowsInstaller') -eq 1) {'MSI'} else {'NSIS'}                Add-Record $Scope $kind $name $display ([string]$entry.GetValue('DisplayVersion')) $path $uninstall ([string]$entry.GetValue('Publisher'))            } finally { $entry.Dispose() }        }    } finally { $key.Dispose() }}function Inventory {    $script:Findings.Clear()    $arp='SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'    $views=@([Microsoft.Win32.RegistryView]::Registry32)    if ([Environment]::Is64BitOperatingSystem) { $views += [Microsoft.Win32.RegistryView]::Registry64 }    foreach ($view in $views) {        $base=[Microsoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine',$view)        try { Read-Uninstall $base $arp 'Machine' } finally { $base.Dispose() }    }    $paths=[Collections.Generic.List[string]]::new()    foreach ($root in @($env:ProgramW6432,$env:ProgramFiles,${env:ProgramFiles(x86)})) {        if ($root) { $paths.Add((Join-Path $root 'VideoLAN\VLC')) }    }    $profiles=[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList')    try {        foreach ($sid in $profiles.GetSubKeyNames()) {            if ($sid -notmatch '^S-1-(5-21|12-1)-') { continue }            $profile=$profiles.OpenSubKey($sid)            try { $homePath=[Environment]::ExpandEnvironmentVariables([string]$profile.GetValue('ProfileImagePath')) } finally { $profile.Dispose() }            if (-not $homePath -or -not (Test-Path -LiteralPath $homePath)) { Problem "Profile unavailable: $sid"; continue }            foreach ($relative in @('AppData\Local\Programs\VideoLAN\VLC','AppData\Local\VideoLAN\VLC','AppData\Roaming\VideoLAN\VLC')) { $paths.Add((Join-Path $homePath $relative)) }            $loaded=[Microsoft.Win32.Registry]::Users.OpenSubKey($sid)            $mount=$sid; $mounted=$false            if ($null -ne $loaded) { $loaded.Dispose() }            else {                $hive=Join-Path $homePath 'NTUSER.DAT'                if (-not (Test-Path -LiteralPath $hive)) { Problem "User hive unavailable: $sid"; continue }                $mount='SmilarVlc_'+[guid]::NewGuid().ToString('N')                & $script:RegExe load "HKU\$mount" $hive 2>&1 | Out-Host                if ($LASTEXITCODE -ne 0) { Problem "Could not inspect unloaded user hive: $sid"; continue }                $mounted=$true            }            try {                foreach ($suffix in @($arp,'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall')) {                    Read-Uninstall ([Microsoft.Win32.Registry]::Users) "$mount\$suffix" "User:$sid"                }            } finally {                if ($mounted) {                    & $script:RegExe unload "HKU\$mount" 2>&1 | Out-Host                    if ($LASTEXITCODE -ne 0) { Problem "Unable to unload temporary hive HKU\$mount" }                }            }        }    } finally { $profiles.Dispose() }    foreach ($path in @($paths.ToArray()+$AdditionalPaths | Sort-Object -Unique)) {        if ($path -and (Test-Path -LiteralPath (Join-Path $path 'vlc.exe')) -and -not @($script:Findings | Where-Object { $_.Path.TrimEnd('\') -eq $path.TrimEnd('\') }).Count) {            Add-Record 'Unregistered' 'PortableOrUnregistered' '' 'VLC media player' '' $path '' ''        }    }    if (Get-Command Get-AppxPackage -ErrorAction SilentlyContinue) {        try {            foreach ($app in @(Get-AppxPackage -AllUsers -ErrorAction Stop | Where-Object { $_.Name -match 'VideoLAN.*VLC|^VLC$' })) {                Add-Record 'Store' 'Appx' $app.PackageFullName $app.Name "$($app.Version)" $app.InstallLocation '' $app.Publisher            }        } catch { Problem 'Store inventory failed; desktop inventory alone is not complete.' }    } else { Problem 'Appx inventory command unavailable; Store inventory not verified.' }    return @($script:Findings.ToArray() | Sort-Object Scope,Kind,Key,Path -Unique)}function Assert-MachineRecord($Record) {    if ($Record.Scope -ne 'Machine' -or $Record.Publisher -notmatch 'VideoLAN') { throw 'Not a recognised machine-wide VideoLAN installation.' }    if (-not $Record.Path -or $Record.Path -match '["\r\n]' -or $Record.Path -notmatch '^[A-Za-z]:\\') { throw 'Invalid installation path.' }    $full=[IO.Path]::GetFullPath($Record.Path).TrimEnd('\')    $roots=@($env:ProgramW6432,$env:ProgramFiles,${env:ProgramFiles(x86)}) | Where-Object { $_ }    if (-not @($roots | Where-Object { $full.StartsWith($_.TrimEnd('\')+'\',[StringComparison]::OrdinalIgnoreCase) }).Count) { throw 'Custom path outside Program Files requires separate review.' }    Assert-NoReparse $full    if (-not (Test-Path -LiteralPath (Join-Path $full 'vlc.exe'))) { throw 'Registered VLC executable is missing.' }    if ($Record.Kind -eq 'MSI') {        if ($Record.Key -notmatch '^\{[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}$') { throw 'Invalid MSI product code.' }    } else {        $uninstaller=Join-Path $full 'uninstall.exe'        if ($Record.Uninstall.Trim('"') -ine $uninstaller -or -not (Test-Path -LiteralPath $uninstaller)) { throw 'Unexpected NSIS uninstall command; will not execute registry command text.' }        Assert-NoReparse $uninstaller        # Never run an uninstaller writable by ordinary users as SYSTEM.        $writeMask=[Security.AccessControl.FileSystemRights]::Write -bor [Security.AccessControl.FileSystemRights]::Delete -bor [Security.AccessControl.FileSystemRights]::ChangePermissions -bor [Security.AccessControl.FileSystemRights]::TakeOwnership        foreach ($check in @($full,$uninstaller)) {            foreach ($ace in (Get-Acl -LiteralPath $check).Access) {                if ($ace.AccessControlType -eq 'Allow' -and ($ace.FileSystemRights -band $writeMask)) {                    $sid=$ace.IdentityReference.Translate([Security.Principal.SecurityIdentifier]).Value                    if ($sid -notin @('S-1-5-18','S-1-5-32-544','S-1-3-0') -and $sid -notlike 'S-1-5-80-*') { throw 'Uninstaller path grants write access beyond trusted system principals.' }                }            }        }    }}function Download-Official([string]$Uri,[string]$Destination) {    if ($Uri -notmatch '^https://download\.videolan\.org/pub/videolan/vlc/') { throw 'Unexpected download origin.' }    Log "DOWNLOAD $Uri"    Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Destination -TimeoutSec 600 -MaximumRedirection 0}function Validate-Package([string]$File,[string]$HashText) {    $match=[regex]::Match($HashText,'(?im)^([a-f0-9]{64})\s+\*?'+[regex]::Escape([IO.Path]::GetFileName($File))+'\s*$')    if (-not $match.Success) { throw 'Malformed or mismatched official SHA-256 manifest.' }    $hash=(Get-FileHash -LiteralPath $File -Algorithm SHA256).Hash    if ($hash -ine $match.Groups[1].Value) { throw 'Installer SHA-256 mismatch.' }    $signature=Get-AuthenticodeSignature -LiteralPath $File    if ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notmatch '(?i)(?:CN|O)=VideoLAN(?:\s|,|$)') { throw 'Installer must have a valid VideoLAN Authenticode signature.' }    Log "VALIDATED SHA256=$hash; signer=$($signature.SignerCertificate.Subject)"}function Run-Installer([string]$File,[string]$Arguments,[string]$Label) {    Log "START $Label; file=$File; arguments=$Arguments"    for ($attempt=1;$attempt -le 3;$attempt++) {        $start=[Diagnostics.ProcessStartInfo]::new()        $start.FileName=$File; $start.Arguments=$Arguments; $start.UseShellExecute=$false; $start.CreateNoWindow=$true        $process=[Diagnostics.Process]::Start($start)        try {            if (-not $process.WaitForExit(900000)) { Log 'TIMEOUT: Installer may still be running; do not retry blindly.'; return 1460 }            $code=$process.ExitCode        } finally { $process.Dispose() }        Log "END $Label; exit=$code; attempt=$attempt"        if ($code -ne 1618 -or $attempt -eq 3) { return $code }        Start-Sleep -Seconds 30    }}function Save-Inventory($Records,[string]$Name) {    ConvertTo-Json -InputObject @($Records) -Depth 5 | Set-Content -LiteralPath (Join-Path $script:RunPath $Name) -Encoding UTF8    foreach ($r in $Records) { Log ('FOUND scope={0}; type={1}; registered={2}; file={3}; path={4}' -f $r.Scope,$r.Kind,$r.Version,$r.FileVersion,$r.Path) }}function Update-Vlc {    $before=@(Inventory)    Save-Inventory $before 'inventory-before.json'    if ($DetectOnly) { Log 'RESULT=DETECTED; inventory only, no downloads or installers.'; if ($script:Problems.Count) { return 2 }; return 0 }    if ($script:PendingPath -and (Test-Path -LiteralPath $script:PendingPath)) { throw 'A previous upgrade is unfinished. Review pending.json and its installer/log directory before recovery; do not remove the marker while an installer is running.' }    if (-not $before.Count) { Log 'RESULT=NOT_INSTALLED'; if ($script:Problems.Count) { return 2 }; return 0 }    $index=(Invoke-WebRequest -UseBasicParsing -Uri 'https://download.videolan.org/pub/videolan/vlc/' -TimeoutSec 60 -MaximumRedirection 0).Content    $release=Latest-Release $index    $target=Parse-Version $release    $script:Target=$release    Log "LATEST_STABLE=$release (official release directory)"    if ($target.Major -ne 3) { throw 'New VLC major release needs installer review. No older release silently substituted.' }    $machine=@($before | Where-Object Scope -eq 'Machine')    foreach ($r in @($before | Where-Object Scope -ne 'Machine')) { Problem "Requires its own deployment method: $($r.Scope) / $($r.Kind) / $($r.Path)" }    if (-not $machine.Count) { return 2 }    $outdated=$false; $blocked=$false    foreach ($r in $machine) {        try { Assert-MachineRecord $r } catch { Problem "Cannot safely update $($r.Path): $($_.Exception.Message)"; $blocked=$true; continue }        $registered=Parse-Version $r.Version; $actual=Parse-Version $r.FileVersion        if ($null -eq $registered -or $null -eq $actual -or $registered -gt $target -or $actual -gt $target) {            Problem "Unknown/newer version: $($r.Path); no downgrade attempted."; $blocked=$true; continue        }        if ($registered -lt $target -or $actual -lt $target) { $outdated=$true }    }    if ($blocked) { return 2 }    if (-not $outdated) { Log 'RESULT=CURRENT'; if ($script:Problems.Count) { return 2 }; return 0 }    $running=@(Get-Process -Name vlc -ErrorAction SilentlyContinue)    if ($running.Count -and -not $CloseRunningVlc) { Log 'RESULT=IN_USE; rerun with -CloseRunningVlc or after VLC closes.'; return 1618 }    $arch=if ([Environment]::Is64BitOperatingSystem) {'win64'} else {'win32'}    $name="vlc-$release-$arch.msi"    $package=Join-Path $script:RunPath $name    $url="https://download.videolan.org/pub/videolan/vlc/$release/$arch/$name"    Download-Official "$url.sha256" "$package.sha256"    Download-Official $url $package    Validate-Package $package (Get-Content -Raw -LiteralPath "$package.sha256")    # Start closing only after the replacement is downloaded and validated.    if ($CloseRunningVlc) {        foreach ($process in @(Get-Process -Name vlc -ErrorAction SilentlyContinue)) {            Log "STOP VLC pid=$($process.Id); playback will end."            Stop-Process -Id $process.Id -Force -ErrorAction Stop        }    }    if (@(Get-Process -Name vlc -ErrorAction SilentlyContinue).Count) { return 1618 }    Log "PLAN: consolidate $($machine.Count) registered desktop copies to $release $arch."    [pscustomobject]@{Target=$release;Package=$package;LogDirectory=$script:RunPath;Started=(Get-Date -Format o);Original=$machine} |        ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $script:PendingPath -Encoding UTF8    $i=0    foreach ($r in $machine) {        $i++        # Refresh and revalidate immediately before using local removal data.        Assert-MachineRecord $r        if ($r.Kind -eq 'MSI') {            $log=Join-Path $script:RunPath "uninstall-$i-msi.log"            $code=Run-Installer $script:MsiExe ('/x {0} /qn /norestart REBOOT=ReallySuppress /L*v "{1}"' -f $r.Key,$log) "Uninstall MSI $($r.Key)"        } else {            $code=Run-Installer (Join-Path $r.Path 'uninstall.exe') ('/S _?={0}' -f $r.Path.TrimEnd('\')) "Uninstall NSIS $($r.Path)"        }        if ($code -in @(3010,1641)) { Log 'RESULT=REBOOT_REQUIRED; no reboot requested; cleanup incomplete.'; return 3010 }        if ($code -notin @(0,1605,1614)) { return $code }    }    # Re-inventory, rather than assuming removal succeeded from exit codes.    $removed=@(Inventory)    Save-Inventory $removed 'inventory-after-removal.json'    if (@($removed | Where-Object Scope -eq 'Machine').Count) { throw 'Old machine registrations remain. Replacement not started.' }    foreach ($r in $machine) {        if (Test-Path -LiteralPath (Join-Path $r.Path 'vlc.exe')) { throw "Old VLC binary remains: $($r.Path). No forced deletion attempted." }    }    $installLog=Join-Path $script:RunPath 'install-msi.log'    $code=Run-Installer $script:MsiExe ('/i "{0}" /qn /norestart REBOOT=ReallySuppress /L*v "{1}"' -f $package,$installLog) "Install VLC $release $arch"    if ($code -in @(3010,1641)) { Log 'RESULT=REBOOT_REQUIRED; no reboot requested; verify after planned restart.'; return 3010 }    if ($code -ne 0) { return $code }    $after=@(Inventory)    Save-Inventory $after 'inventory-after.json'    $desktop=@($after | Where-Object Scope -eq 'Machine')    if (-not $desktop.Count) { throw 'No desktop registration found after installation.' }    foreach ($r in $desktop) {        if ((Parse-Version $r.Version) -ne $target -or (Parse-Version $r.FileVersion) -ne $target) { throw "Version verification failed: $($r.Path)" }    }    Remove-Item -LiteralPath $script:PendingPath -Force    if (@($after | Where-Object Scope -ne 'Machine').Count -or $script:Problems.Count) { Log 'RESULT=PARTIAL; desktop updated, unresolved inventory remains.'; return 2 }    Remove-Item -LiteralPath $package -Force    Log 'RESULT=UPDATED; all discovered registered desktop copies are at the target release.'    return 0}$exitCode=1; $mutex=$null; $locked=$false; $transcript=$falsetry {    if (-not [Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { throw 'Run this script as LocalSystem / SYSTEM.' }    if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { throw 'ARM64 Windows requires a separate deployment.' }    if ([Environment]::OSVersion.Version.Major -lt 10) { throw 'This updater targets Windows 10/11 and Server 2016 or later.' }    if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) { throw 'Select 64-bit PowerShell for complete inventory on 64-bit Windows.' }    $script:MsiExe=Join-Path $env:WINDIR 'System32\msiexec.exe'    $script:RegExe=Join-Path $env:WINDIR 'System32\reg.exe'    $mutex=[Threading.Mutex]::new($false,'Global\SmilarVlcUpdater')    try { $locked=$mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $locked=$true }    if (-not $locked) { $exitCode=1618; throw 'Another updater instance is running.' }    Init-Log; $transcript=$true    [Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12    $exitCode=Update-Vlc} catch { Log "RESULT=ERROR; $($_.Exception.Message)" } finally {    if ($script:RunPath) {        try {            $final=@(Inventory)            Save-Inventory $final 'inventory-final.json'            if ($exitCode -eq 0 -and $script:Problems.Count) { $exitCode=2 }            [pscustomobject]@{ExitCode=$exitCode;Target=$script:Target;Time=(Get-Date -Format o);Unresolved=@($script:Problems.ToArray());Inventory=$final} |                ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $script:RunPath 'summary.json') -Encoding UTF8            Log "EXIT_CODE=$exitCode; LOG_DIRECTORY=$script:RunPath"        } catch { Log "Final inventory/logging failed: $($_.Exception.Message)"; if ($exitCode -eq 0) { $exitCode=1 } }    }    if ($transcript) { $null=Stop-Transcript }    if ($locked) { $mutex.ReleaseMutex() }    if ($null -ne $mutex) { $mutex.Dispose() }}exit $exitCode

Expected results

ExitMeaning
0Discovered desktop copies verified current/updated, no VLC found, or detection completed. Check the logged result.
2Incomplete inventory or unresolved copies. Supported desktop copies may have been updated; review summary.json.
1618VLC is in use, another updater is running, or Windows Installer remains busy.
3010Restart required; no reboot requested. Upgrade is not declared complete.
1460Installer timed out and may still be running. Do not launch a duplicate.
1 / other MSI codeFailure. Review the transcript and MSI logs before retrying.
Recovery after an interrupted upgrade

Removal starts only after the replacement is downloaded and validated. A failure after removal can still leave VLC unavailable. The installer and logs are retained, with a protected %ProgramData%\Smilar\VlcUpdater\pending.json marker. Later runs stop instead of falsely reporting “not installed” as success. Review the recorded plan, finish recovery with the validated installer and confirm the result before clearing that marker. Never clear it while an installer is running. Automatic rollback is not provided.

Script integrity · SHA-2565544b358af92e00005683d750411775f6e725a7e2fc80160a9a4c4eea5a14e00