PrintToVeraeTimeStamp/platform/windows/scripts/install-printer.ps1

154 lines
9.9 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"
)
$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"
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)"
$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
Step "Helper autostart (HKLM Run)" {
Set-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' -Name VeraePrintHelper -Value "`"$Helper`""
} { (Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run').VeraePrintHelper -like "*verae-printhelper.exe*" } | 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) {
$helperUp = Step "Helper listening on $IppAddr" {
Get-Process verae-printhelper -ErrorAction SilentlyContinue | ForEach-Object { Write-Host " existing helper pid=$($_.Id)" }
if (-not (Test-Ipp)) {
# explorer.exe launches the exe with the (non-elevated) shell token = the real user session
Write-Host " starting helper un-elevated via explorer.exe"
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 " explorer launch did not bring IPP up; starting elevated temporary helper for discovery"
Start-Process -FilePath $Helper -WindowStyle Hidden
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" }
}
# ---------------- 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.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"
Stop-Transcript | Out-Null
if ($okAny) { exit 0 } else { exit 1 }