282 lines
9.5 KiB
PowerShell
282 lines
9.5 KiB
PowerShell
param(
|
|
[string]$LogPath,
|
|
[string]$InstDir,
|
|
[string]$TargetPath,
|
|
[string]$LockerListPath,
|
|
[string]$Session,
|
|
[string]$Version,
|
|
[string]$Arch,
|
|
[string]$Updated,
|
|
[string]$CurrentOutDir
|
|
)
|
|
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
|
|
function Write-InstallerJson([string]$event, [hashtable]$fields) {
|
|
$payload = [ordered]@{
|
|
schemaVersion = 1
|
|
ts = (Get-Date -Format o)
|
|
session = $Session
|
|
version = $Version
|
|
arch = $Arch
|
|
updated = ($Updated -eq '1' -or $Updated -eq 'true')
|
|
instDir = $InstDir
|
|
event = $event
|
|
}
|
|
foreach ($key in $fields.Keys) {
|
|
$payload[$key] = $fields[$key]
|
|
}
|
|
Add-Content -LiteralPath $LogPath -Encoding UTF8 -Value ($payload | ConvertTo-Json -Compress -Depth 10)
|
|
}
|
|
|
|
function Test-SamePath([string]$left, [string]$right) {
|
|
if ([string]::IsNullOrWhiteSpace($left) -or [string]::IsNullOrWhiteSpace($right)) {
|
|
return $false
|
|
}
|
|
try {
|
|
$leftFull = [System.IO.Path]::GetFullPath($left).TrimEnd('\')
|
|
$rightFull = [System.IO.Path]::GetFullPath($right).TrimEnd('\')
|
|
return [string]::Equals($leftFull, $rightFull, [System.StringComparison]::CurrentCultureIgnoreCase)
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function New-SelfLockProcess([int]$processId) {
|
|
return [pscustomobject]@{ name = 'AionUi installer'; pid = $processId }
|
|
}
|
|
|
|
function Write-LockersAndExit($lockers, [string]$fallbackReason, [string]$message, [int]$exitCode, [int]$resources, [int]$count) {
|
|
$lockerText = @($lockers | ForEach-Object { $_.name + '(' + $_.pid + ')' }) -join ', '
|
|
[System.IO.File]::WriteAllText($LockerListPath, $lockerText, (New-Object System.Text.UTF8Encoding $false))
|
|
Write-InstallerJson 'rm-lockers' @{
|
|
target = $TargetPath
|
|
resources = $resources
|
|
count = $count
|
|
blockingProcesses = @($lockers)
|
|
fallbackReason = $fallbackReason
|
|
message = $message
|
|
outerInstallerPid = $script:installerPid
|
|
currentOutDir = $CurrentOutDir
|
|
installerSelfLock = $script:installerSelfLock
|
|
}
|
|
exit $exitCode
|
|
}
|
|
|
|
[System.IO.File]::WriteAllText($LockerListPath, '', (New-Object System.Text.UTF8Encoding $false))
|
|
|
|
try {
|
|
$instDirFull = [System.IO.Path]::GetFullPath($InstDir)
|
|
$targetPathFull = if ($TargetPath) { [System.IO.Path]::GetFullPath($TargetPath) } else { '' }
|
|
$psProc = @(Get-CimInstance -ClassName Win32_Process | Where-Object { $_.ProcessId -eq $PID })[0]
|
|
$script:installerPid = if ($psProc) { [int]$psProc.ParentProcessId } else { 0 }
|
|
$script:installerSelfLock = (Test-SamePath $CurrentOutDir $targetPathFull) -or (Test-SamePath $CurrentOutDir $instDirFull)
|
|
|
|
$resources = @()
|
|
if ($targetPathFull -and (Test-Path -LiteralPath $targetPathFull -PathType Leaf)) {
|
|
$resources = @($targetPathFull)
|
|
} elseif ($targetPathFull -and (Test-Path -LiteralPath $targetPathFull -PathType Container)) {
|
|
$topLevel = @(Get-ChildItem -LiteralPath $targetPathFull -Force -File -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName })
|
|
$knownRelative = @(
|
|
'AionUi.exe',
|
|
'Uninstall AionUi.exe',
|
|
'resources\app.asar',
|
|
'resources\app-update.yml',
|
|
'resources\bundled-aioncore\win32-x64\aioncore.exe'
|
|
)
|
|
$known = @(
|
|
$knownRelative |
|
|
ForEach-Object { Join-Path $targetPathFull $_ } |
|
|
Where-Object { Test-Path -LiteralPath $_ -PathType Leaf }
|
|
)
|
|
$resources = @($topLevel + $known | Where-Object { $_ -and $_.Trim().Length -gt 0 } | Select-Object -Unique | Select-Object -First 512)
|
|
}
|
|
|
|
Write-InstallerJson 'rm-query-start' @{
|
|
target = $TargetPath
|
|
resources = $resources.Count
|
|
outerInstallerPid = $script:installerPid
|
|
currentOutDir = $CurrentOutDir
|
|
installerSelfLock = $script:installerSelfLock
|
|
}
|
|
|
|
if ($resources.Count -eq 0) {
|
|
if ($script:installerSelfLock -and $script:installerPid -gt 0) {
|
|
Write-LockersAndExit @(New-SelfLockProcess $script:installerPid) `
|
|
'installer-self-lock' `
|
|
'The installer process is using the install directory as its current output directory.' `
|
|
0 `
|
|
0 `
|
|
1
|
|
}
|
|
Write-LockersAndExit @() `
|
|
'restart-manager-no-resources' `
|
|
'Restart Manager had no existing files to query for this path.' `
|
|
1 `
|
|
0 `
|
|
0
|
|
}
|
|
|
|
$source = @'
|
|
using System;
|
|
using System.Text;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace AionUi.RestartManager {
|
|
public enum RM_APP_TYPE {
|
|
RmUnknownApp = 0,
|
|
RmMainWindow = 1,
|
|
RmOtherWindow = 2,
|
|
RmService = 3,
|
|
RmExplorer = 4,
|
|
RmConsole = 5,
|
|
RmCritical = 1000
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct RM_UNIQUE_PROCESS {
|
|
public int dwProcessId;
|
|
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
public struct RM_PROCESS_INFO {
|
|
public RM_UNIQUE_PROCESS Process;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
|
public string strAppName;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
|
public string strServiceShortName;
|
|
public RM_APP_TYPE ApplicationType;
|
|
public uint AppStatus;
|
|
public uint TSSessionId;
|
|
[MarshalAs(UnmanagedType.Bool)]
|
|
public bool bRestartable;
|
|
}
|
|
|
|
public static class Native {
|
|
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
|
|
public static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, StringBuilder strSessionKey);
|
|
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
|
|
public static extern int RmRegisterResources(uint dwSessionHandle, UInt32 nFiles, string[] rgsFilenames, UInt32 nApplications, IntPtr rgApplications, UInt32 nServices, string[] rgsServiceNames);
|
|
[DllImport("rstrtmgr.dll")]
|
|
public static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons);
|
|
[DllImport("rstrtmgr.dll")]
|
|
public static extern int RmEndSession(uint pSessionHandle);
|
|
}
|
|
}
|
|
'@
|
|
Add-Type -TypeDefinition $source -ErrorAction Stop
|
|
|
|
$sessionHandle = [uint32]0
|
|
$key = New-Object System.Text.StringBuilder 64
|
|
$result = [AionUi.RestartManager.Native]::RmStartSession([ref]$sessionHandle, 0, $key)
|
|
if ($result -ne 0) {
|
|
throw "RmStartSession=$result"
|
|
}
|
|
|
|
try {
|
|
for ($i = 0; $i -lt $resources.Count; $i += 256) {
|
|
$end = [Math]::Min($i + 255, $resources.Count - 1)
|
|
$chunk = [string[]]$resources[$i..$end]
|
|
$result = [AionUi.RestartManager.Native]::RmRegisterResources($sessionHandle, [uint32]$chunk.Count, $chunk, 0, [IntPtr]::Zero, 0, $null)
|
|
if ($result -ne 0) {
|
|
throw "RmRegisterResources=$result"
|
|
}
|
|
}
|
|
|
|
$ERROR_MORE_DATA = 234
|
|
$ERROR_ACCESS_DENIED = 5
|
|
$needed = [uint32]0
|
|
$count = [uint32]0
|
|
$reasons = [uint32]0
|
|
|
|
for ($attempt = 0; $attempt -lt 6; $attempt++) {
|
|
if ($attempt -gt 0) {
|
|
Start-Sleep -Milliseconds (50 * $attempt)
|
|
}
|
|
$needed = [uint32]0
|
|
$count = [uint32]0
|
|
$reasons = [uint32]0
|
|
$result = [AionUi.RestartManager.Native]::RmGetList($sessionHandle, [ref]$needed, [ref]$count, $null, [ref]$reasons)
|
|
if ($result -ne $ERROR_ACCESS_DENIED) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($result -ne 0 -and $result -ne $ERROR_MORE_DATA) {
|
|
throw "RmGetList=$result"
|
|
}
|
|
|
|
$lockers = @()
|
|
if ($result -eq $ERROR_MORE_DATA -or $needed -gt 0) {
|
|
for ($attempt = 0; $attempt -lt 6; $attempt++) {
|
|
if ($attempt -gt 0) {
|
|
Start-Sleep -Milliseconds (50 * $attempt)
|
|
}
|
|
$count = $needed
|
|
$apps = New-Object 'AionUi.RestartManager.RM_PROCESS_INFO[]' $count
|
|
$result = [AionUi.RestartManager.Native]::RmGetList($sessionHandle, [ref]$needed, [ref]$count, $apps, [ref]$reasons)
|
|
if ($result -ne $ERROR_ACCESS_DENIED -and $result -ne $ERROR_MORE_DATA) {
|
|
break
|
|
}
|
|
}
|
|
if ($result -ne 0) {
|
|
throw "RmGetList=$result"
|
|
}
|
|
$lockers = @(
|
|
$apps |
|
|
Select-Object -First $count |
|
|
Where-Object { $_.Process.dwProcessId -gt 0 } |
|
|
ForEach-Object {
|
|
$name = $_.strAppName
|
|
if (-not $name) {
|
|
$proc = Get-Process -Id $_.Process.dwProcessId -ErrorAction SilentlyContinue
|
|
if ($proc) {
|
|
$name = $proc.ProcessName
|
|
}
|
|
}
|
|
if (-not $name) {
|
|
$name = 'unknown'
|
|
}
|
|
[pscustomobject]@{ name = $name; pid = [int]$_.Process.dwProcessId }
|
|
}
|
|
)
|
|
}
|
|
|
|
if ($lockers.Count -eq 0 -and $script:installerSelfLock -and $script:installerPid -gt 0) {
|
|
$lockers = @(New-SelfLockProcess $script:installerPid)
|
|
}
|
|
|
|
if ($script:installerSelfLock -and $lockers.Count -gt 0) {
|
|
Write-LockersAndExit $lockers `
|
|
'installer-self-lock' `
|
|
'The installer process is using the install directory as its current output directory.' `
|
|
0 `
|
|
$resources.Count `
|
|
$lockers.Count
|
|
}
|
|
|
|
if ($lockers.Count -eq 0) {
|
|
Write-LockersAndExit @() `
|
|
'restart-manager-no-process' `
|
|
'Windows did not identify a specific locking process. Close terminals, editors, and file managers opened in the install folder.' `
|
|
1 `
|
|
$resources.Count `
|
|
0
|
|
}
|
|
|
|
Write-LockersAndExit $lockers '' '' 0 $resources.Count $needed
|
|
} finally {
|
|
[void][AionUi.RestartManager.Native]::RmEndSession($sessionHandle)
|
|
}
|
|
} catch {
|
|
Write-InstallerJson 'rm-error' @{
|
|
target = $TargetPath
|
|
error = $_.Exception.Message
|
|
outerInstallerPid = $script:installerPid
|
|
currentOutDir = $CurrentOutDir
|
|
installerSelfLock = $script:installerSelfLock
|
|
}
|
|
exit 1
|
|
}
|