198 lines
13 KiB
PowerShell
198 lines
13 KiB
PowerShell
# Verae TimeStamp PDF — printer installer (DEBUG build). Must run elevated.
|
|
# Every Add-* is followed by a Get-* verification. Full transcript + JSON summary
|
|
# go to %ProgramData%\Verae\logs. Exit code 0 = at least one printer verified.
|
|
param(
|
|
[string]$InstallDir = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)),
|
|
[switch]$SkipIpp,
|
|
[switch]$SkipFallback,
|
|
[string]$IppAddr = "127.0.0.1:8631",
|
|
[string]$ResultIni = ""
|
|
)
|
|
$ErrorActionPreference = "Continue"
|
|
$PrinterName = "Verae TimeStamp PDF"
|
|
$FallbackName = "Verae TimeStamp PDF (Local Port)"
|
|
$LogDir = Join-Path $env:ProgramData "Verae\logs"
|
|
$DropDir = Join-Path $env:ProgramData "Verae\spool\fallback"
|
|
$PortPath = Join-Path $DropDir "verae-job.pdf"
|
|
$Bin = Join-Path $InstallDir "bin"
|
|
$Helper = Join-Path $Bin "verae-printhelper.exe"
|
|
$Cli = Join-Path $Bin "verae-print.exe"
|
|
$IppUrl = "http://$IppAddr/ipp/print"
|
|
$ts = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
. (Join-Path $PSScriptRoot "common.ps1")
|
|
$TaskName = "Verae Print Helper"
|
|
New-Item -ItemType Directory -Force -Path $LogDir, $DropDir | Out-Null
|
|
$Transcript = Join-Path $LogDir "install-$ts.log"
|
|
Start-Transcript -Path $Transcript -Force | Out-Null
|
|
|
|
$summary = [ordered]@{ started = (Get-Date).ToString("o"); steps = @(); ipp = "not-run"; fallback = "not-run" }
|
|
function Step([string]$Name, [scriptblock]$Do, [scriptblock]$Verify) {
|
|
Write-Host "`n=== STEP: $Name"
|
|
$err = $null; $ok = $false
|
|
try { & $Do | Out-Host } catch { $err = $_; Write-Host " EXCEPTION: $($_.Exception.GetType().FullName): $($_.Exception.Message) HResult=0x$('{0:X8}' -f $_.Exception.HResult)" }
|
|
if ($Verify) {
|
|
try { $ok = [bool](& $Verify) } catch { Write-Host " VERIFY EXCEPTION: $($_.Exception.Message)" }
|
|
} else { $ok = ($null -eq $err) }
|
|
Write-Host (" RESULT: " + $(if ($ok) { "OK" } else { "FAIL" }))
|
|
$script:summary.steps += [ordered]@{ step = $Name; ok = $ok; error = $(if ($err) { "$($err.Exception.Message)" } else { $null }) }
|
|
return $ok
|
|
}
|
|
|
|
Write-Host "Verae printer install (debug) $ts"
|
|
Write-Host "InstallDir=$InstallDir User=$(whoami) PS=$($PSVersionTable.PSVersion) 64bitProcess=$([Environment]::Is64BitProcess)"
|
|
$os = Get-CimInstance Win32_OperatingSystem
|
|
Write-Host "OS: $($os.Caption) $($os.Version) build $($os.BuildNumber) $($os.OSArchitecture)"
|
|
$Target = Get-VeraeTargetUser
|
|
Write-Host "Target (logged-in) user: $($Target.Account) sid=$($Target.Sid) profile=$($Target.Profile) via $($Target.Source)"
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
Write-Host "Elevated: $isAdmin"
|
|
if (-not $isAdmin) { Write-Host "ERROR: not elevated — printer/port creation will fail (Access denied)." }
|
|
if (-not [Environment]::Is64BitProcess -and [Environment]::Is64BitOperatingSystem) { Write-Host "WARNING: 32-bit PowerShell on 64-bit Windows (WOW64) — should be launched via Sysnative." }
|
|
|
|
Write-Host "`n--- WPP / policy registry"
|
|
foreach ($k in 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\WPP','HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers','HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\PointAndPrint') {
|
|
if (Test-Path $k) { Write-Host "[$k]"; Get-ItemProperty $k | Format-List | Out-String | Write-Host } else { Write-Host "(absent) $k" }
|
|
}
|
|
Write-Host "--- BEFORE: printers / drivers / ports"
|
|
Get-Printer | Format-Table Name,DriverName,PortName,Type -AutoSize | Out-String -Width 250 | Write-Host
|
|
Get-PrinterDriver | Format-Table Name,MajorVersion,PrinterEnvironment -AutoSize | Out-String -Width 250 | Write-Host
|
|
Get-PrinterPort | Format-Table Name,Description,PortMonitor -AutoSize | Out-String -Width 250 | Write-Host
|
|
|
|
Step "Spooler running" { $s = Get-Service Spooler; if ($s.Status -ne 'Running') { Start-Service Spooler } } { (Get-Service Spooler).Status -eq 'Running' } | Out-Null
|
|
Step "Enable PrintService/Operational log" { wevtutil sl Microsoft-Windows-PrintService/Operational /e:true | Out-Host } { (Get-WinEvent -ListLog Microsoft-Windows-PrintService/Operational).IsEnabled } | Out-Null
|
|
Step "ACL logs+spool dirs (Users: Modify)" {
|
|
icacls (Join-Path $env:ProgramData "Verae") /grant "*S-1-5-32-545:(OI)(CI)M" /T /C | Out-Host
|
|
} { Test-Path $LogDir } | Out-Null
|
|
Step "Debug flag HKLM\Software\Verae\PrintStamp\Debug=1" {
|
|
New-Item -Path 'HKLM:\Software\Verae\PrintStamp' -Force | Out-Null
|
|
Set-ItemProperty 'HKLM:\Software\Verae\PrintStamp' -Name Debug -Value 1 -Type DWord
|
|
Set-ItemProperty 'HKLM:\Software\Verae\PrintStamp' -Name InstallDir -Value $InstallDir
|
|
} { (Get-ItemProperty 'HKLM:\Software\Verae\PrintStamp').Debug -eq 1 } | Out-Null
|
|
$script:taskOk = Step "Helper autostart: scheduled task '$TaskName' for $($Target.Account) (at logon, non-elevated)" {
|
|
Remove-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' -Name VeraePrintHelper -ErrorAction SilentlyContinue
|
|
$action = New-ScheduledTaskAction -Execute $Helper -WorkingDirectory $Bin
|
|
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $Target.Account
|
|
$principal = New-ScheduledTaskPrincipal -UserId $Target.Account -LogonType Interactive -RunLevel Limited
|
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -MultipleInstances IgnoreNew -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
|
|
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description "Verae TimeStamp PDF print helper (IPP loopback 127.0.0.1:8631)" -Force | Out-Null
|
|
} { $t = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue; if ($t) { Write-Host " task principal=$($t.Principal.UserId) runlevel=$($t.Principal.RunLevel)" }; [bool]$t }
|
|
Step "Remove old per-user 0.1.2 install for $($Target.Account)" {
|
|
$old = Join-Path $Target.Profile "AppData\Local\Verae\PrintStamp\bin\verae-print.exe"
|
|
if (Test-Path $old) { Remove-Item -Force $old; Write-Host " removed $old" } else { Write-Host " none" }
|
|
Remove-Item "Registry::HKEY_USERS\$($Target.Sid)\Software\Microsoft\Windows\CurrentVersion\Uninstall\VeraePrintStamp" -Recurse -ErrorAction SilentlyContinue
|
|
} $null | Out-Null
|
|
Step "Add bin to machine PATH" {
|
|
$p = [Environment]::GetEnvironmentVariable("Path","Machine")
|
|
if ($p -notlike "*$Bin*") { [Environment]::SetEnvironmentVariable("Path", ($p.TrimEnd(';') + ";" + $Bin), "Machine") }
|
|
} { [Environment]::GetEnvironmentVariable("Path","Machine") -like "*$Bin*" } | Out-Null
|
|
|
|
function Test-Ipp { try { $r = Invoke-WebRequest -UseBasicParsing -TimeoutSec 3 "http://$IppAddr/"; return ($r.StatusCode -eq 200) } catch { return $false } }
|
|
|
|
# ---------------- Primary: IPP class driver → loopback helper ----------------
|
|
if (-not $SkipIpp) {
|
|
$script:tempHelper = $null
|
|
$helperUp = Step "Start helper as $($Target.Account) and wait for $IppAddr" {
|
|
if (-not (Test-Ipp)) {
|
|
if ($script:taskOk) { Write-Host " Start-ScheduledTask '$TaskName'"; Start-ScheduledTask -TaskName $TaskName }
|
|
for ($i = 0; $i -lt 30 -and -not (Test-Ipp); $i++) { Start-Sleep -Milliseconds 500 }
|
|
}
|
|
if (-not (Test-Ipp)) {
|
|
Write-Host " task did not bring IPP up; trying explorer.exe (shell token) launch"
|
|
Start-Process explorer.exe -ArgumentList "`"$Helper`""
|
|
for ($i = 0; $i -lt 20 -and -not (Test-Ipp); $i++) { Start-Sleep -Milliseconds 500 }
|
|
}
|
|
if (-not (Test-Ipp)) {
|
|
Write-Host " still down; starting a TEMPORARY elevated helper only for printer discovery (stopped afterwards)"
|
|
$script:tempHelper = Start-Process -FilePath $Cli -ArgumentList "serve --no-watch" -WindowStyle Hidden -PassThru
|
|
for ($i = 0; $i -lt 20 -and -not (Test-Ipp); $i++) { Start-Sleep -Milliseconds 500 }
|
|
}
|
|
& $Cli ipp-selftest --addr $IppAddr | Out-Host
|
|
} { Test-Ipp }
|
|
if ($helperUp) {
|
|
Get-Printer -Name $PrinterName -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " removing stale '$($_.Name)'"; Remove-Printer -Name $_.Name }
|
|
$before = @(Get-Printer | Select-Object -ExpandProperty Name)
|
|
$hasIppUrl = (Get-Command Add-Printer).Parameters.ContainsKey('IppURL')
|
|
Write-Host "Add-Printer supports -IppURL: $hasIppUrl"
|
|
$added = Step "Add IPP printer ($IppUrl)" {
|
|
if ($hasIppUrl) {
|
|
Add-Printer -IppURL $IppUrl -ErrorAction Stop
|
|
} else {
|
|
Add-Printer -Name $PrinterName -DriverName "Microsoft IPP Class Driver" -PortName $IppUrl -ErrorAction Stop
|
|
}
|
|
} {
|
|
Start-Sleep 2
|
|
$new = @(Get-Printer | Where-Object { $before -notcontains $_.Name })
|
|
Write-Host " new printers: $(( $new | ForEach-Object { "$($_.Name) [$($_.DriverName) / $($_.PortName)]" }) -join '; ')"
|
|
$script:newIpp = $new | Select-Object -First 1
|
|
($null -ne $script:newIpp) -or (Get-Printer -Name $PrinterName -ErrorAction SilentlyContinue)
|
|
}
|
|
if ($added -and $script:newIpp -and $script:newIpp.Name -ne $PrinterName) {
|
|
Step "Rename '$($script:newIpp.Name)' → '$PrinterName'" { Rename-Printer -Name $script:newIpp.Name -NewName $PrinterName -ErrorAction Stop } { Get-Printer -Name $PrinterName -ErrorAction SilentlyContinue } | Out-Null
|
|
}
|
|
$p = Get-Printer -Name $PrinterName -ErrorAction SilentlyContinue
|
|
if ($p) { $p | Format-List * | Out-String | Write-Host; $summary.ipp = "OK: driver=$($p.DriverName) port=$($p.PortName)" } else { $summary.ipp = "FAIL" }
|
|
} else { $summary.ipp = "FAIL: helper not reachable" }
|
|
}
|
|
|
|
if ($script:tempHelper) { Write-Host "Stopping temporary elevated helper pid=$($script:tempHelper.Id)"; Stop-Process -Id $script:tempHelper.Id -Force -ErrorAction SilentlyContinue }
|
|
$script:helperOwner = ""
|
|
$helperRunning = Step "Helper running as logged-in user $($Target.Account)" { } {
|
|
$p = Get-CimInstance Win32_Process -Filter "Name='verae-printhelper.exe'" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if (-not $p) { Write-Host " helper process not found"; return $false }
|
|
$o = Invoke-CimMethod -InputObject $p -MethodName GetOwner
|
|
$script:helperOwner = "$($o.Domain)\$($o.User)"
|
|
Write-Host " helper pid=$($p.ProcessId) owner=$script:helperOwner session=$($p.SessionId)"
|
|
($script:helperOwner -eq $Target.Account) -and (Test-Ipp)
|
|
}
|
|
|
|
# ---------------- Fallback: Microsoft Print To PDF → Local file port ----------------
|
|
if (-not $SkipFallback) {
|
|
$drv = Step "Driver 'Microsoft Print To PDF' present" {
|
|
if (-not (Get-PrinterDriver -Name "Microsoft Print To PDF" -ErrorAction SilentlyContinue)) { Add-PrinterDriver -Name "Microsoft Print To PDF" -ErrorAction Stop }
|
|
} { Get-PrinterDriver -Name "Microsoft Print To PDF" -ErrorAction SilentlyContinue }
|
|
$port = Step "Local port '$PortPath'" {
|
|
if (-not (Get-PrinterPort -Name $PortPath -ErrorAction SilentlyContinue)) { Add-PrinterPort -Name $PortPath -ErrorAction Stop }
|
|
} { Get-PrinterPort -Name $PortPath -ErrorAction SilentlyContinue }
|
|
if ($drv -and $port) {
|
|
Get-Printer -Name $FallbackName -ErrorAction SilentlyContinue | ForEach-Object { Remove-Printer -Name $_.Name }
|
|
$fb = Step "Add printer '$FallbackName'" {
|
|
Add-Printer -Name $FallbackName -DriverName "Microsoft Print To PDF" -PortName $PortPath -ErrorAction Stop
|
|
} { Get-Printer -Name $FallbackName -ErrorAction SilentlyContinue }
|
|
$summary.fallback = $(if ($fb) { "OK" } else { "FAIL" })
|
|
} else { $summary.fallback = "FAIL: driver=$drv port=$port" }
|
|
}
|
|
|
|
Write-Host "`n--- AFTER: printers / ports"
|
|
Get-Printer | Format-Table Name,DriverName,PortName,Type,PrinterStatus -AutoSize | Out-String -Width 250 | Write-Host
|
|
Get-PrinterPort | Format-Table Name,Description,PortMonitor -AutoSize | Out-String -Width 250 | Write-Host
|
|
Write-Host "--- PrintService events (last 15 min)"
|
|
foreach ($ln in 'Microsoft-Windows-PrintService/Admin','Microsoft-Windows-PrintService/Operational') {
|
|
Get-WinEvent -FilterHashtable @{LogName=$ln; StartTime=(Get-Date).AddMinutes(-15)} -ErrorAction SilentlyContinue |
|
|
Format-Table TimeCreated,Id,LevelDisplayName,Message -Wrap | Out-String -Width 250 | Write-Host
|
|
}
|
|
$summary.target = $Target.Account
|
|
$summary.helperOwner = $script:helperOwner
|
|
$summary.finished = (Get-Date).ToString("o")
|
|
$summary.transcript = $Transcript
|
|
$summaryPath = Join-Path $LogDir "install-summary-$ts.json"
|
|
$summary | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 $summaryPath
|
|
$okAny = ($summary.ipp -like "OK*") -or ($summary.fallback -eq "OK")
|
|
Write-Host "`nVerae printer install: $(if ($okAny) {'OK'} else {'FAIL'}) ipp=[$($summary.ipp)] fallback=[$($summary.fallback)]"
|
|
Write-Host "Log: $Transcript"
|
|
Write-Host "Summary: $summaryPath"
|
|
if ($ResultIni) {
|
|
$stepOk = { param($pat) $x = $summary.steps | Where-Object { $_.step -like $pat } | Select-Object -Last 1; if ($null -eq $x) { "SKIP" } elseif ($x.ok) { "OK" } else { "FAIL" } }
|
|
Write-VeraeIni $ResultIni "printer" ([ordered]@{
|
|
target = $Target.Account
|
|
spooler = (& $stepOk "Spooler running")
|
|
oplog = (& $stepOk "Enable PrintService*")
|
|
task = (& $stepOk "Helper autostart*")
|
|
helper = $(if ($helperRunning) { "OK ($script:helperOwner)" } else { "FAIL ($script:helperOwner)" })
|
|
ipp = $summary.ipp
|
|
fallback = $summary.fallback
|
|
overall = $(if ($okAny) { "OK" } else { "FAIL" })
|
|
transcript = $Transcript
|
|
summary = $summaryPath })
|
|
}
|
|
Stop-Transcript | Out-Null
|
|
if ($okAny) { exit 0 } else { exit 1 }
|