#Requires -RunAsAdministrator <# .SYNOPSIS Unattended installation script for Laserfiche Webtools Agent, Scanning, and OCR client components .DESCRIPTION Automatically detects and installs the latest unpacked Laserfiche component builds from a specified directory. Prerequisites are installed automatically from package.manifest files, and detailed installation logs are created for troubleshooting. Features: - .NET Framework 4.8 prerequisite check and auto-installation - Auto-detection of latest component versions - Intelligent version comparison (skip if already installed/upgraded) - Automatic prerequisite installation with logging - Sequential installation with error handling - Comprehensive timestamped logs - Proper reboot handling (exit code 3010) .PARAMETER LfPackageBaseDirectory The directory containing the unpacked Laserfiche component folders. This directory should contain subdirectories like "ScanningSetup 12.0.xxxx.xxx", "OCR 12.0.xxxx.xxx", etc. .PARAMETER LfInstallLogBaseDirectory The directory where installation logs will be created. Each component gets a timestamped subdirectory. Default: C:\temp\InstallLogs\ .PARAMETER EnableTranscript Enables full PowerShell transcript logging. Captures all console output to a transcript file in the log directory. Useful for detailed troubleshooting and audit trails. .EXAMPLE Basic usage with required package directory: .\Install-LFClientComponents.ps1 -LfPackageBaseDirectory "C:\LF_Packages\LF12_2025H2" Specify custom log directory: .\Install-LFClientComponents.ps1 -LfPackageBaseDirectory "C:\LF_Packages\LF12_2025H2" -LfInstallLogBaseDirectory "D:\Logs\LF_Install" SCCM Deployment Example: Program Command Line: powershell.exe -ExecutionPolicy Bypass -NoProfile -File ".\Install-LFClientComponents.ps1" -LfPackageBaseDirectory "%CD%\LFPackages" -LfInstallLogBaseDirectory "C:\Windows\Temp\LF_Install" Program Properties: - Run: Whether or not a user is logged on - Run mode: Run with administrative rights - Drive mode: Runs with UNC name Detection Method: Use Test-LFComponentsInstalled function or check for installed product Preparation steps: 1. Download Laserfiche Installer from: https://support.laserfiche.com/download/4213/laserfiche-12 2. Use Laserfiche Installer to download component packages Documentation: https://doc.laserfiche.com/laserfiche.documentation/12/userguide/en-us/content/install-installer.htm 3. Extract package .exe files with 7-Zip or similar tool 4. (Optional) Download .NET Framework 4.8 or 4.8.1 *Offline* Runtime installer to use instead of the web installer: https://dotnet.microsoft.com/download/dotnet-framework/net48 https://dotnet.microsoft.com/download/dotnet-framework/net481 Place ndp48-x86-x64-allos-enu.exe or ndp481-x86-x64-allos-enu.exe in the package directory for auto-installation in environments with restricted internet access 5. Copy extracted folders to your package directory (for manual) or deployment manager (SCCM, etc.) package source 6. Run this script with -LfPackageBaseDirectory pointing to that directory .NOTES File Name : Install-LFClientComponents.ps1 Prerequisite : PowerShell 5.1 or higher, Administrator privileges, extracted Laserfiche component packages Min LF Release Tested : 2025H2 Max LF Release Tested : 2025H2 Last Updated : 2025-10-21 MIT License Copyright (c) 2025 Laserfiche Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #> [CmdletBinding()] param( [Parameter(Mandatory=$true, HelpMessage="Directory containing unpacked Laserfiche component packages")] [ValidateScript({ if (-not (Test-Path $_ -PathType Container)) { throw "Directory does not exist: $_" } return $true })] [string]$LfPackageBaseDirectory, [Parameter(Mandatory=$false)] [string]$LfInstallLogBaseDirectory = 'C:\temp\InstallLogs\', [Parameter(Mandatory=$false)] [switch]$EnableTranscript ) # Verify administrative privileges $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) $isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Error "This script requires administrative privileges to install Laserfiche components." Write-Error "Please run PowerShell as Administrator and try again." exit 1 } Write-Output "Administrative privileges verified." # Ensure log directory exists before transcript if (-not (Test-Path $LfInstallLogBaseDirectory)) { New-Item -ItemType Directory -Force -Path $LfInstallLogBaseDirectory | Out-Null Write-Output "Created log directory: $LfInstallLogBaseDirectory" } # Start transcript logging if enabled if ($EnableTranscript) { $transcriptPath = Join-Path -Path $LfInstallLogBaseDirectory -ChildPath "Install-Transcript_$(Get-Date -Format 'yyyy-MM-dd_HHmmss').log" Start-Transcript -Path $transcriptPath -Force Write-Output "Transcript logging enabled: $transcriptPath" } # Define exit code constants $script:EXIT_SUCCESS = 0 $script:EXIT_SUCCESS_REBOOT_REQUIRED = 3010 $script:EXIT_FAILURE = 1 # Initialize exit code tracking for deployment managers $script:InstallationErrors = @() $script:ComponentsInstalled = @() $script:RebootRequired = $false # Initialize build number variables [string]$LfWebtoolsAgentBuildNum = '' [string]$LfScanningBuildNum = '' [string]$LfOCRBuildNum = '' # Initialize presence flags [bool]$LfWebtoolsAgentPresent = $false [bool]$LfScanningPresent = $false [bool]$LfOCRPresent = $false <# .SYNOPSIS Checks if Laserfiche components are installed and returns their versions .DESCRIPTION Used as a detection method for deployment manager tools (SCCM, Intune, PDQ Deploy, etc.). Returns installed components and their versions. Can be used to determine if installation/upgrade is needed. Uses registry-based detection (Add/Remove Programs) which is fast and doesn't trigger MSI consistency checks. Queries both 64-bit and 32-bit registry paths for comprehensive detection. .EXAMPLE # Use as deployment manager (SCCM etc.) detection method $detection = Test-LFComponentsInstalled if ($detection.ScanningInstalled -and $detection.ScanningVersion -ge [version]"12.0.2507.188") { Write-Output "Already installed" exit 0 } #> function Test-LFComponentsInstalled { try { $result = [PSCustomObject]@{ WebToolsAgentInstalled = $false WebToolsAgentVersion = $null ScanningInstalled = $false ScanningVersion = $null OCRInstalled = $false OCRVersion = $null } # Query registry paths for installed programs (both 64-bit and 32-bit) $registryPaths = @( 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) $installedPrograms = @() foreach ($path in $registryPaths) { if (Test-Path $path) { $installedPrograms += Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -and $_.Publisher -like "*Laserfiche*" } | Select-Object DisplayName, DisplayVersion } } # Match Laserfiche components foreach ($program in $installedPrograms) { if ($program.DisplayName -like "Laserfiche Webtools Agent*") { $result.WebToolsAgentInstalled = $true $result.WebToolsAgentVersion = $program.DisplayVersion } elseif ($program.DisplayName -like "Laserfiche Scanning*") { $result.ScanningInstalled = $true $result.ScanningVersion = $program.DisplayVersion } elseif ($program.DisplayName -like "Laserfiche OCR with OmniPage*") { $result.OCRInstalled = $true $result.OCRVersion = $program.DisplayVersion } } return $result } catch { Write-Output "Warning: Could not query installed programs: ${_}" return $null } } <# .SYNOPSIS Checks if .NET Framework 4.8 or higher is installed .DESCRIPTION Verifies that .NET Framework 4.8 or higher (including 4.8.1) is installed, which is required for Laserfiche 12.x components. Runtime updates are cumulative, so 4.8.1 (Release 533320+) includes all of 4.8. Returns true if installed, false otherwise. .OUTPUTS Boolean indicating if .NET Framework 4.8+ is installed #> function Test-DotNetFramework48Installed { try { # Check registry for .NET Framework 4.8+ (Release >= 528040 for 4.8, >= 533320 for 4.8.1) $netFxKey = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction SilentlyContinue if ($null -ne $netFxKey -and $netFxKey.Release -ge 528040) { # Determine version string $versionStr = if ($netFxKey.Release -ge 533320) { "4.8.1" } else { "4.8" } Write-Output ".NET Framework $versionStr or higher detected (Release: $($netFxKey.Release))" return $true } else { Write-Warning ".NET Framework 4.8+ not detected. Current release: $($netFxKey.Release)" return $false } } catch { Write-Warning "Could not determine .NET Framework version: ${_}" return $false } } <# .SYNOPSIS Installs .NET Framework 4.8 or 4.8.1 if not already present .PARAMETER InstallerPath Path to the .NET Framework installer: - ndp48-x86-x64-allos-enu.exe (.NET 4.8 - recommended for Windows Server 2016 compatibility) - ndp481-x86-x64-allos-enu.exe (.NET 4.8.1 - for Server 2019+, Win 10/11) Note: 4.8.1 is cumulative and includes all 4.8 features, but not compatible with Server 2016. .PARAMETER LogDirectory Directory where installation logs will be written .OUTPUTS Returns exit code from installer (3010 = reboot required) #> function Install-DotNetFramework48 { param( [Parameter(Mandatory=$false)] [string]$InstallerPath, [Parameter(Mandatory=$true)] [string]$LogDirectory ) Write-Output "`nInstalling .NET Framework 4.8+..." $logFile = Join-Path -Path $LogDirectory -ChildPath "dotnet_install.log" if ([string]::IsNullOrEmpty($InstallerPath) -or -not (Test-Path $InstallerPath)) { Write-Warning "No .NET Framework installer found." Write-Output "Download .NET 4.8 or 4.8.1 : https://dotnet.microsoft.com/download/dotnet-framework/net48 | https://dotnet.microsoft.com/download/dotnet-framework/net481" return -1 } try { Write-Output " This may take 5-10 minutes..." $process = Start-Process -FilePath $InstallerPath -ArgumentList "/q /norestart /log `"$logFile`"" -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode if ($exitCode -eq 0) { Write-Output " SUCCESS: .NET Framework installed" return 0 } elseif ($exitCode -eq 3010 -or $exitCode -eq 1641) { Write-Warning " .NET Framework installed - REBOOT REQUIRED" return 3010 } else { Write-Error " Installation failed with exit code: $exitCode. Check log: $logFile" return $exitCode } } catch { Write-Error "Failed to install .NET Framework: ${_}" return -1 } } <# .SYNOPSIS Gets the latest version of Laserfiche components in a directory .PARAMETER BaseDirectory The directory containing unpacked Laserfiche component folders .PARAMETER Prefixes Array of folder prefixes to search for (e.g., "OCR", "ScanningSetup") .OUTPUTS Hashtable with component names as keys and version strings as values #> function Get-LatestComponentVersions { param( [Parameter(Mandatory=$true)] [string]$BaseDirectory, [Parameter(Mandatory=$true)] [string[]]$Prefixes ) $results = @{} foreach ($prefix in $Prefixes) { # Get all matching folders $folders = Get-ChildItem -Path $BaseDirectory -Directory | Where-Object { $_.Name -like "$prefix *" } # Extract version numbers and find the highest one $versions = $folders | ForEach-Object { if ($_ -match "$prefix\s+(\d+\.\d+\.\d+\.\d+)$") { [version]$matches[1] } } if ($versions.Count -gt 0) { $maxVersion = ($versions | Sort-Object -Descending)[0] $results[$prefix] = $maxVersion.ToString() } } return $results } <# .SYNOPSIS Creates a timestamped log directory for a Laserfiche component installation .PARAMETER BaseLogDirectory The base directory where log folders are stored .PARAMETER ComponentName The name of the component (e.g., "LFScanning", "LFOCR") .PARAMETER BuildNumber The build number/version of the component .OUTPUTS The full path to the created log directory #> function New-ComponentLogDirectory { param( [Parameter(Mandatory=$true)] [string]$BaseLogDirectory, [Parameter(Mandatory=$true)] [string]$ComponentName, [Parameter(Mandatory=$true)] [string]$BuildNumber ) $logPath = Join-Path -Path $BaseLogDirectory -ChildPath "$ComponentName-$BuildNumber\$(Get-Date -Format 'yyyy-MM-dd_HHmmss')" New-Item -ItemType Directory -Force -Path $logPath | Out-Null return $logPath } # ------------------------------------ # Check .NET Framework 4.8 Prerequisite # ------------------------------------ Write-Output "`n=== Prerequisite Check: .NET Framework 4.8+ ===" if (-not (Test-DotNetFramework48Installed)) { Write-Warning ".NET Framework 4.8 or higher is required for Laserfiche 12.x components." # Look for .NET Framework installer in package directory (4.8 or 4.8.1) $dotNetInstallers = @() $dotNetInstallers += Get-ChildItem -Path $LfPackageBaseDirectory -Filter "ndp48*.exe" -ErrorAction SilentlyContinue $dotNetInstallers += Get-ChildItem -Path $LfPackageBaseDirectory -Filter "ndp481*.exe" -ErrorAction SilentlyContinue if ($dotNetInstallers.Count -gt 0) { # Detect OS version to choose compatible .NET Framework version when both runtime installers are present # .NET 4.8.1 requires Windows 10 version 1809 (build 17763) or newer / Server 2019+ $osVersion = [System.Environment]::OSVersion.Version $supports481 = ($osVersion.Major -gt 10) -or ($osVersion.Major -eq 10 -and $osVersion.Build -ge 17763) if (-not $supports481) { # Server 2016 or older / Win 10 pre-1809: Use .NET 4.8 only (4.8.1 not supported) $dotNetPath = ($dotNetInstallers | Where-Object { $_.Name -like "*ndp48-*" -and $_.Name -notlike "*ndp481*" } | Select-Object -First 1).FullName if (-not $dotNetPath) { # Fallback to any available installer $dotNetPath = $dotNetInstallers[0].FullName Write-Warning "Older OS detected (pre-Server 2019/Win10-1809) but only .NET 4.8.1 found - installation may fail" } } else { # Server 2019+, Win 10 1809+, Win 11: Prefer .NET 4.8.1 (cumulative update) $dotNetPath = ($dotNetInstallers | Where-Object { $_.Name -like "*ndp481*" } | Select-Object -First 1).FullName if (-not $dotNetPath) { # Fallback to .NET 4.8 $dotNetPath = ($dotNetInstallers | Where-Object { $_.Name -like "*ndp48-*" } | Select-Object -First 1).FullName } } Write-Output "Found .NET Framework installer: $dotNetPath" $exitCode = Install-DotNetFramework48 -InstallerPath $dotNetPath -LogDirectory $LfInstallLogBaseDirectory if ($exitCode -eq 3010 -or $exitCode -eq 1641) { Write-Output "`n=== REBOOT REQUIRED ===" Write-Output ".NET Framework has been installed but requires a system reboot." Write-Output "Please reboot the system and re-run this script to continue installation." if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_SUCCESS_REBOOT_REQUIRED } elseif ($exitCode -ne 0) { $errorMsg = ".NET Framework installation failed" Write-Error $errorMsg $script:InstallationErrors += $errorMsg if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_FAILURE } } else { Write-Warning "No .NET Framework installer found in package directory." Write-Output "Download from:" Write-Output " .NET 4.8 (recommended): https://dotnet.microsoft.com/download/dotnet-framework/net48" Write-Output " .NET 4.8.1 (alternative): https://dotnet.microsoft.com/download/dotnet-framework/net481" Write-Output "`nUse .NET 4.8 for widest OS compatibility (includes Server 2016)" Write-Output "Note: .NET 4.8.1 is cumulative but requires Server 2019+/Win10+/Win11" Write-Output "`nPlace offline installer (ndp48-x86-x64-allos-enu.exe) in: $LfPackageBaseDirectory" Write-Output "`nContinuing with installation - components may fail if .NET 4.8+ is required and the bundled .NET 4.8 web installer is not able to download the package from Microsoft and install it..." } } else { Write-Output ".NET Framework 4.8+ prerequisite satisfied" } # ------------------------------------ # Automatically find the latest unpacked builds in the folder $LfPackageBaseDirectory # ------------------------------------ Write-Output "`n=== Detecting Component Packages ===" $prefixes = @("OCR", "ScanningSetup", "WebToolsAgent") $highestVersions = Get-LatestComponentVersions -BaseDirectory $LfPackageBaseDirectory -Prefixes $prefixes # Set presence flags and build numbers based on discovered versions if ($highestVersions.ContainsKey("WebToolsAgent")) { $LfWebtoolsAgentPresent = $true $LfWebtoolsAgentBuildNum = $highestVersions["WebToolsAgent"] } if ($highestVersions.ContainsKey("ScanningSetup")) { $LfScanningPresent = $true $LfScanningBuildNum = $highestVersions["ScanningSetup"] } if ($highestVersions.ContainsKey("OCR")) { $LfOCRPresent = $true $LfOCRBuildNum = $highestVersions["OCR"] } Write-Output "`nPackage Detection Results:" Write-Output " Webtools Agent Package: $LfWebtoolsAgentPresent (Version: $LfWebtoolsAgentBuildNum)" Write-Output " Scanning Package: $LfScanningPresent (Version: $LfScanningBuildNum)" Write-Output " OCR Package: $LfOCRPresent (Version: $LfOCRBuildNum)" # Check if any packages were found if (-not $LfWebtoolsAgentPresent -and -not $LfScanningPresent -and -not $LfOCRPresent) { Write-Output "`n=== ERROR: NO PACKAGES FOUND ===" Write-Warning "No Laserfiche component packages found in: $LfPackageBaseDirectory" Write-Output "`nExpected folder structure:" Write-Output " - WebToolsAgent [version]" Write-Output " - ScanningSetup [version]" Write-Output " - OCR [version]" Write-Output "`nExample: WebToolsAgent 11.0.2509.11" if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_FAILURE } # ------------------------------------ # Check Already Installed Components # ------------------------------------ Write-Output "`n=== Checking Installed Components ===" $installedComponents = Test-LFComponentsInstalled if ($null -ne $installedComponents) { Write-Output "`nCurrently Installed:" Write-Output " Webtools Agent: $($installedComponents.WebToolsAgentInstalled) (Version: $($installedComponents.WebToolsAgentVersion))" Write-Output " Scanning: $($installedComponents.ScanningInstalled) (Version: $($installedComponents.ScanningVersion))" Write-Output " OCR: $($installedComponents.OCRInstalled) (Version: $($installedComponents.OCRVersion))" # Determine what needs to be installed/upgraded if ($LfWebtoolsAgentPresent -and $installedComponents.WebToolsAgentInstalled) { if ([version]$LfWebtoolsAgentBuildNum -le [version]$installedComponents.WebToolsAgentVersion) { Write-Output "`n -> Webtools Agent $($installedComponents.WebToolsAgentVersion) already installed (skipping $LfWebtoolsAgentBuildNum)" $LfWebtoolsAgentPresent = $false } else { Write-Output "`n -> Webtools Agent will be upgraded: $($installedComponents.WebToolsAgentVersion) -> $LfWebtoolsAgentBuildNum" } } if ($LfScanningPresent -and $installedComponents.ScanningInstalled) { if ([version]$LfScanningBuildNum -le [version]$installedComponents.ScanningVersion) { Write-Output " -> Scanning $($installedComponents.ScanningVersion) already installed (skipping $LfScanningBuildNum)" $LfScanningPresent = $false } else { Write-Output " -> Scanning will be upgraded: $($installedComponents.ScanningVersion) -> $LfScanningBuildNum" } } if ($LfOCRPresent -and $installedComponents.OCRInstalled) { if ([version]$LfOCRBuildNum -le [version]$installedComponents.OCRVersion) { Write-Output " -> OCR $($installedComponents.OCRVersion) already installed (skipping $LfOCRBuildNum)" $LfOCRPresent = $false } else { Write-Output " -> OCR will be upgraded: $($installedComponents.OCRVersion) -> $LfOCRBuildNum" } } # Check if everything is already up-to-date if (-not $LfWebtoolsAgentPresent -and -not $LfScanningPresent -and -not $LfOCRPresent) { Write-Output "`n=== ALL COMPONENTS UP TO DATE ===" Write-Output "All detected components are already installed at the same or newer version." Write-Output "No installation necessary." if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_SUCCESS } } else { Write-Output "`nNo installed components detected (fresh installation)" } <# .SYNOPSIS Installs prerequisites from a package.manifest file .PARAMETER SetupDir The directory containing the package.manifest file .PARAMETER LogDirectory The directory where prerequisite installation logs will be written #> function Install-PrerequisitesFromManifest { param( [Parameter(Mandatory=$true)] [string]$SetupDir, [Parameter(Mandatory=$true)] [string]$LogDirectory ) $manifestPath = Join-Path -Path $SetupDir -ChildPath "package.manifest" if (-not (Test-Path $manifestPath)) { Write-Warning "No package.manifest found - skipping prerequisite installation" return } try { # Read and clean manifest (remove trailing commas that break JSON parsing) $manifestJson = Get-Content $manifestPath -Raw $manifestJson = $manifestJson -replace ',(\s*[\]}])', '$1' # Remove trailing commas $manifest = $manifestJson | ConvertFrom-Json if ($null -eq $manifest.Prereqs -or $manifest.Prereqs.Count -eq 0) { Write-Output " No prerequisites required" return } Write-Output " Installing $($manifest.Prereqs.Count) prerequisite(s)..." foreach ($prereq in $manifest.Prereqs) { $installerPath = Join-Path -Path $SetupDir -ChildPath $prereq.Path if (-not (Test-Path $installerPath)) { Write-Warning " Prerequisite not found: $($prereq.Name) - skipping" continue } Write-Output " - $($prereq.Name)" try { $process = Start-Process -FilePath $installerPath -ArgumentList $prereq.Commandline -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode if ($exitCode -ne 0 -and $exitCode -ne 3010) { Write-Warning " Exit code $exitCode (may be optional)" } } catch { Write-Warning " Installation failed: ${_}" } } } catch { Write-Error "Failed to process package.manifest: ${_}" } } # ------------------------------------ # Laserfiche Webtools Agent # ------------------------------------ # Note: Webtools Agent 11.x uses SetupLf.exe, while 12.x will likely use an MSI. This code will need to be updated for 12.x. if($LfWebtoolsAgentPresent) { if($LfWebtoolsAgentBuildNum -like "11.*") { Write-Output "`nInstalling Laserfiche Webtools Agent $LfWebtoolsAgentBuildNum..." $LfWebtoolsAgentSetupDir = Join-Path -Path $LfPackageBaseDirectory -ChildPath "WebToolsAgent $LfWebtoolsAgentBuildNum\ClientWeb" $LfWebtoolsAgentLogPath = New-ComponentLogDirectory -BaseLogDirectory $LfInstallLogBaseDirectory -ComponentName "LFWebToolsAgent" -BuildNumber $LfWebtoolsAgentBuildNum $LfWebtoolsAgentExe = Join-Path -Path $LfWebtoolsAgentSetupDir -ChildPath "SetupLf.exe" # Validate installer exists if (-not (Test-Path $LfWebtoolsAgentExe)) { $errorMsg = "Webtools Agent installer not found: $LfWebtoolsAgentExe" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } else { try { # Invoke SetupLf.exe directly. Call operator waits for completion and populates $LASTEXITCODE. $arguments = @('-log', $LfWebtoolsAgentLogPath, '-silent') & $LfWebtoolsAgentExe @arguments $exitCode = $LASTEXITCODE if ($exitCode -eq $script:EXIT_SUCCESS -or $exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { Write-Output " SUCCESS: Webtools Agent installed (Exit code: $exitCode)" $script:ComponentsInstalled += "Webtools Agent $LfWebtoolsAgentBuildNum" if ($exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { $script:RebootRequired = $true } } else { $errorMsg = "Webtools Agent installation failed with exit code: $exitCode" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } catch { $errorMsg = "Webtools Agent installation error: ${_}" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } } } # ------------------------------------ # Laserfiche Scanning # ------------------------------------ if($LfScanningPresent) { Write-Output "`nInstalling Laserfiche Scanning $LfScanningBuildNum..." $LfScanningSetupDir = Join-Path -Path $LfPackageBaseDirectory -ChildPath "ScanningSetup $LfScanningBuildNum" # MSI requires specific log file in /log arg input rather than directory $LfScanningLogPath = New-ComponentLogDirectory -BaseLogDirectory $LfInstallLogBaseDirectory -ComponentName "LFScanning" -BuildNumber $LfScanningBuildNum $LfScanningLogFile = Join-Path -Path $LfScanningLogPath -ChildPath "lfscanning.msi.log" # Install prerequisites from package.manifest Install-PrerequisitesFromManifest -SetupDir $LfScanningSetupDir -LogDirectory $LfScanningLogPath # Install Scanning MSI with absolute path $LfScanningMsi = Join-Path -Path $LfScanningSetupDir -ChildPath "Install\lfscanning.msi" # Validate MSI exists if (-not (Test-Path $LfScanningMsi)) { $errorMsg = "Scanning MSI not found: $LfScanningMsi" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } else { try { # Start process and wait for completion $process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$LfScanningMsi`" /quiet /norestart /log `"$LfScanningLogFile`"" -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode if ($exitCode -eq $script:EXIT_SUCCESS -or $exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { Write-Output " SUCCESS: Scanning installed (Exit code: $exitCode)" $script:ComponentsInstalled += "Scanning $LfScanningBuildNum" if ($exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { $script:RebootRequired = $true } } else { $errorMsg = "Scanning installation failed with exit code: $exitCode" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } catch { $errorMsg = "Scanning installation error: ${_}" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } } # ------------------------------------ # Laserfiche OCR with OmniPage 22.2 # ------------------------------------ if($LfOCRPresent) { Write-Output "`nInstalling Laserfiche OCR $LfOCRBuildNum..." $LfOCRSetupDir = Join-Path -Path $LfPackageBaseDirectory -ChildPath "OCR $LfOCRBuildNum" # MSI requires specific log file in /log arg input rather than directory $LfOCRLogPath = New-ComponentLogDirectory -BaseLogDirectory $LfInstallLogBaseDirectory -ComponentName "LFOCR" -BuildNumber $LfOCRBuildNum $LfOCRLogFile = Join-Path -Path $LfOCRLogPath -ChildPath "OCRIntaller-x64.msi.log" # Install prerequisites from package.manifest Install-PrerequisitesFromManifest -SetupDir $LfOCRSetupDir -LogDirectory $LfOCRLogPath # Install OCR MSI with absolute path $LfOCRMsi = Join-Path -Path $LfOCRSetupDir -ChildPath "OCRInstaller-x64.msi" # Validate MSI exists if (-not (Test-Path $LfOCRMsi)) { $errorMsg = "OCR MSI not found: $LfOCRMsi" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } else { try { # Start process and wait for completion $process = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$LfOCRMsi`" /quiet /norestart /log `"$LfOCRLogFile`"" -Wait -PassThru -NoNewWindow $exitCode = $process.ExitCode if ($exitCode -eq $script:EXIT_SUCCESS -or $exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { Write-Output " SUCCESS: OCR installed (Exit code: $exitCode)" $script:ComponentsInstalled += "OCR $LfOCRBuildNum" if ($exitCode -eq $script:EXIT_SUCCESS_REBOOT_REQUIRED) { $script:RebootRequired = $true } } else { $errorMsg = "OCR installation failed with exit code: $exitCode" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } catch { $errorMsg = "OCR installation error: ${_}" Write-Error $errorMsg $script:InstallationErrors += $errorMsg } } } # ------------------------------------ # Final Installation Summary and Exit Code # ------------------------------------ Write-Output "`n=== Installation Summary ===" if ($script:ComponentsInstalled.Count -gt 0) { Write-Output "`nSuccessfully Installed Components:" foreach ($component in $script:ComponentsInstalled) { Write-Output " - $component" } } if ($script:InstallationErrors.Count -gt 0) { Write-Output "`nInstallation Errors:" foreach ($errorMsg in $script:InstallationErrors) { Write-Output " - $errorMsg" } Write-Output "`nInstallation completed with errors. Check logs at: $LfInstallLogBaseDirectory" # Stop transcript if enabled if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_FAILURE } else { Write-Output "`nAll installations completed successfully!" Write-Output "Logs saved to: $LfInstallLogBaseDirectory" # Check if reboot is required if ($script:RebootRequired) { Write-Output "`n=== REBOOT REQUIRED ===" Write-Warning 'One or more components require a system reboot to complete installation.' Write-Output 'Please reboot the system at your earliest convenience.' Write-Output '`nDeployment managers will detect this via exit code 3010.' # Stop transcript if enabled if ($EnableTranscript) { Stop-Transcript } exit $script:EXIT_SUCCESS_REBOOT_REQUIRED } # Stop transcript if enabled if ($EnableTranscript) { Stop-Transcript } # Exit with success code - no reboot needed exit $script:EXIT_SUCCESS } # End of script