|
1 | | -<# |
| 1 | +<# |
2 | 2 | .SYNOPSIS |
3 | | - PowerShell Script for Deploying GLPI Agent via GPO. |
| 3 | + Deploy/upgrade GLPI Agent via GPO without blocking startup. |
4 | 4 |
|
5 | 5 | .DESCRIPTION |
6 | | - This script installs and configures the GLPI Agent on workstations using Group Policy (GPO). |
7 | | - It ensures seamless inventory management and reporting within an enterprise environment. |
8 | | - The script also retrieves the %USERDOMAIN% environment variable and sends it as a TAG to the GLPI server. |
| 6 | + - Detects installed GLPI Agent version from registry (and falls back to the EXE file version). |
| 7 | + - If the same major/minor version is already installed, it skips installation (idempotent). |
| 8 | + - If different/not found, runs MSI with silent parameters and logs to C:\Scripts-LOGS. |
| 9 | + - Avoids uninstalling older versions; relies on MSI’s built-in upgrade/replace logic. |
| 10 | + - Detects GPO Startup context and does NOT wait on msiexec in that path (prevents boot hang). |
9 | 11 |
|
10 | 12 | .AUTHOR |
11 | | - Luiz Hamilton Silva - @brazilianscriptguy |
| 13 | + Luiz Hamilton Silva (@brazilianscriptguy) – adapted for GPO-safe, idempotent flow. |
12 | 14 |
|
13 | 15 | .VERSION |
14 | | - Last Updated: February 14, 2025 |
| 16 | + Last Updated: 2025-09-10 |
15 | 17 | #> |
16 | 18 |
|
17 | | -param ( |
18 | | - # Path to the GLPI Agent MSI installer (desired version) |
19 | | - [string]$GLPIAgentMSI = "\\headq.scriptguy\netlogon\glpi-agent112-install.msi", |
20 | | - # Directory where logs will be recorded |
21 | | - [string]$GLPILogDir = "C:\Logs-TEMP", |
22 | | - # Expected version after installation |
23 | | - [string]$ExpectedVersion = "1.12" |
| 19 | +param( |
| 20 | + # Path to the GLPI Agent MSI installer (desired version) |
| 21 | + [string]$GLPIAgentMSI = "\\headq.scriptguy\netlogon\glpi-agent115-install.msi", |
| 22 | + # Directory where logs will be recorded |
| 23 | + [string]$GLPILogDir = "C:\Logs-TEMP", |
| 24 | + # Target version string to compare (prefix match, e.g. '1.15') |
| 25 | + [string]$ExpectedVersion = "1.15", |
| 26 | + # GLPI server URL and TAG to write during MSI install |
| 27 | + [string]$ServerUrl = "http://cmdb.headq.scriptguy/front/inventory.php", |
| 28 | + [string]$TagOverride = $null |
24 | 29 | ) |
25 | 30 |
|
26 | | -# Immediately stop execution in case of an error |
27 | | -$ErrorActionPreference = "Stop" |
| 31 | +$ErrorActionPreference = 'Stop' |
28 | 32 |
|
29 | | -# Define log file name and path |
30 | | -$scriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name) |
31 | | -$logFileName = "${scriptName}.log" |
32 | | -$logPath = Join-Path $GLPILogDir $logFileName |
| 33 | +# ------------------------ Logging ------------------------ |
| 34 | +$scriptName = [IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name) |
| 35 | +$logFileName = "$scriptName.log" |
| 36 | +$logPath = Join-Path $GLPILogDir $logFileName |
33 | 37 |
|
34 | | -# Get the user’s domain from the environment variable; if not set, use a default value. |
35 | | -$userDomain = $env:USERDOMAIN |
36 | | -if (-not $userDomain) { |
37 | | - $userDomain = "UNKNOWN_DOMAIN" |
38 | | -} |
39 | | - |
40 | | -############################################################################### |
41 | | -# FUNCTION: Log-Message |
42 | | -# Logs messages to the log file. In case of an error, also logs to the EventLog. |
43 | | -############################################################################### |
44 | 38 | function Log-Message { |
45 | | - param ( |
46 | | - [Parameter(Mandatory = $true)] |
47 | | - [string]$Message, |
48 | | - [string]$Severity = "INFO" |
49 | | - ) |
50 | | - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" |
51 | | - $logEntry = "[$Severity] [$timestamp] $Message" |
52 | | - try { |
53 | | - Add-Content -Path $logPath -Value $logEntry -ErrorAction Stop |
54 | | - } |
55 | | - catch { |
56 | | - Write-EventLog -LogName Application -Source "GLPI-Agent-Install" -EntryType Error -EventId 1 -Message "Failed to write to log at $logPath. Error: $_" |
57 | | - } |
| 39 | + param( |
| 40 | + [Parameter(Mandatory=$true)][string]$Message, |
| 41 | + [ValidateSet('INFO','WARNING','ERROR')][string]$Level='INFO' |
| 42 | + ) |
| 43 | + $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' |
| 44 | + $line = "[$ts] [$Level] $Message" |
| 45 | + try { Add-Content -Path $logPath -Value $line -ErrorAction Stop } catch { Write-Host $line } |
58 | 46 | } |
59 | 47 |
|
60 | | -# Ensure the log directory exists |
61 | | -if (-not (Test-Path $GLPILogDir)) { |
62 | | - New-Item -Path $GLPILogDir -ItemType Directory -Force | Out-Null |
63 | | - Log-Message "Log directory $GLPILogDir created." |
| 48 | +# Ensure log directory |
| 49 | +if (-not (Test-Path -Path $GLPILogDir)) { |
| 50 | + try { New-Item -Path $GLPILogDir -ItemType Directory -Force | Out-Null } catch {} |
64 | 51 | } |
65 | 52 |
|
66 | | -############################################################################### |
67 | | -# FUNCTION: Uninstall-GLPIAgent |
68 | | -# Uninstalls the current installation of GLPI Agent using the Registry value. |
69 | | -############################################################################### |
70 | | -function Uninstall-GLPIAgent { |
71 | | - param ( |
72 | | - [string]$RegistryKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\GLPI-Agent" |
73 | | - ) |
74 | | - Log-Message "Checking for GLPI Agent in the registry..." |
75 | | - try { |
76 | | - $key = Get-ItemProperty -Path $RegistryKey -ErrorAction SilentlyContinue |
77 | | - if ($key) { |
78 | | - $uninstallString = $key.UninstallString |
79 | | - if ($uninstallString) { |
80 | | - Log-Message "Removing installed version of GLPI Agent..." |
81 | | - # Execute the uninstallation command silently |
82 | | - Start-Process -FilePath "cmdbexe" -ArgumentList "/c $uninstallString /quiet /norestart" -Wait -NoNewWindow |
83 | | - Log-Message "GLPI Agent successfully removed." |
84 | | - } else { |
85 | | - Log-Message "Key found, but UninstallString is empty." -Severity "WARNING" |
86 | | - } |
87 | | - } else { |
88 | | - Log-Message "GLPI Agent not found in the registry." |
89 | | - } |
90 | | - } |
91 | | - catch { |
92 | | - Log-Message "Error while uninstalling GLPI Agent: $_" -Severity "WARNING" |
93 | | - } |
| 53 | +# -------------------- Context detection -------------------- |
| 54 | +function Get-IsGpoStartup { |
| 55 | + try { |
| 56 | + $isSystem = ([Security.Principal.WindowsIdentity]::GetCurrent().User.Value -eq 'S-1-5-18') |
| 57 | + $session0 = ([Diagnostics.Process]::GetCurrentProcess().SessionId -eq 0) |
| 58 | + $pp = Get-CimInstance Win32_Process -Filter "ProcessId=$pid" |
| 59 | + $parent = if ($pp.ParentProcessId) { Get-Process -Id $pp.ParentProcessId -ErrorAction SilentlyContinue } |
| 60 | + $pname = ($parent.Name -replace '\.exe$','').ToLower() |
| 61 | + $gpParents = @('gpscript','gpupdate','winlogon','services') |
| 62 | + return ($isSystem -and $session0 -and $gpParents -contains $pname) |
| 63 | + } catch { return $false } |
94 | 64 | } |
95 | 65 |
|
96 | | -############################################################################### |
97 | | -# FUNCTION: Install-GLPIAgent |
98 | | -# Installs the GLPI Agent via MSI. |
99 | | -############################################################################### |
100 | | -function Install-GLPIAgent { |
101 | | - param ( |
102 | | - [string]$InstallerPath |
103 | | - ) |
104 | | - Log-Message "Starting installation of GLPI Agent version $ExpectedVersion..." |
105 | | - # Define installation parameters: /quiet, RUNNOW=1, SERVER, and TAG |
106 | | - $installArgs = "/quiet RUNNOW=1 SERVER='http://cmdb.headq.scriptguy/front/inventory.php' TAG='$userDomain'" |
107 | | - try { |
108 | | - Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$InstallerPath`" $installArgs" -Wait -NoNewWindow -ErrorAction Stop |
109 | | - Log-Message "GLPI Agent version $ExpectedVersion installed successfully." |
110 | | - } |
111 | | - catch { |
112 | | - Log-Message "Error while installing GLPI Agent: $_" -Severity "WARNING" |
113 | | - exit 1 |
114 | | - } |
115 | | -} |
| 66 | +$IsGpoStartup = Get-IsGpoStartup |
| 67 | +Log-Message "GPO Startup context: $IsGpoStartup" |
116 | 68 |
|
117 | | -############################################################################### |
118 | | -# FUNCTION: Get-GLPIAgentPath |
119 | | -# Returns the path of the GLPI Agent executable, checking known locations. |
120 | | -############################################################################### |
| 69 | +# -------------------- Utilities -------------------- |
121 | 70 | function Get-GLPIAgentPath { |
122 | | - $possiblePaths = @( |
123 | | - "C:\Program Files\GLPI-Agent\glpi-agent.exe", |
124 | | - "C:\Program Files (x86)\GLPI-Agent\glpi-agent.exe", |
125 | | - "C:\Program Files\GLPI-Agent\perl\bin\glpi-agent.exe" |
126 | | - ) |
127 | | - |
128 | | - foreach ($path in $possiblePaths) { |
129 | | - if (Test-Path $path) { |
130 | | - return $path |
| 71 | + $candidates = @( |
| 72 | + "C:\Program Files\GLPI-Agent\glpi-agent.exe", |
| 73 | + "C:\Program Files (x86)\GLPI-Agent\glpi-agent.exe", |
| 74 | + "C:\Program Files\GLPI-Agent\perl\bin\glpi-agent.exe" |
| 75 | + ) |
| 76 | + foreach ($p in $candidates) { if (Test-Path $p) { return $p } } |
| 77 | + return $null |
| 78 | +} |
| 79 | + |
| 80 | +function Get-InstalledGLPIInfo { |
| 81 | + $roots = @( |
| 82 | + "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", |
| 83 | + "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" |
| 84 | + ) |
| 85 | + foreach ($r in $roots) { |
| 86 | + Get-ChildItem $r -ErrorAction SilentlyContinue | ForEach-Object { |
| 87 | + $it = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue |
| 88 | + if ($it -and $it.DisplayName -and ($it.DisplayName -like "*GLPI Agent*")) { |
| 89 | + [PSCustomObject]@{ |
| 90 | + DisplayName = $it.DisplayName |
| 91 | + DisplayVersion = $it.DisplayVersion |
| 92 | + UninstallString = $it.UninstallString |
| 93 | + InstallLocation = $it.InstallLocation |
| 94 | + RegistryKey = $_.PSChildName |
131 | 95 | } |
| 96 | + } |
132 | 97 | } |
133 | | - return $null |
| 98 | + } |
134 | 99 | } |
135 | 100 |
|
136 | | -############################################################################### |
137 | | -# FUNCTION: Configure-GLPIAgent |
138 | | -# Applies configuration to the GLPI Agent by calling the executable with parameters. |
139 | | -############################################################################### |
140 | | -function Configure-GLPIAgent { |
141 | | - param ( |
142 | | - [string]$AgentPath |
143 | | - ) |
144 | | - Log-Message "Configuring GLPI Agent for domain: $userDomain..." |
145 | | - $configArgs = "--server=http://cmdb.headq.scriptguy/front/inventory.php --tag='$userDomain' --debug" |
146 | | - try { |
147 | | - Start-Process -FilePath $AgentPath -ArgumentList $configArgs -Wait -NoNewWindow -ErrorAction Stop |
148 | | - Log-Message "GLPI Agent configuration successfully applied." |
149 | | - } |
150 | | - catch { |
151 | | - Log-Message "Error while configuring GLPI Agent: $_" -Severity "WARNING" |
152 | | - exit 1 |
153 | | - } |
| 101 | +# Resolve domain/TAG |
| 102 | +$domainTag = if ($TagOverride) { $TagOverride } elseif ($env:USERDOMAIN) { $env:USERDOMAIN } else { |
| 103 | + try { (Get-CimInstance Win32_ComputerSystem).Domain } catch { 'UNKNOWN' } |
154 | 104 | } |
155 | 105 |
|
156 | | -############################################################################### |
157 | | -# MAIN SCRIPT FLOW |
158 | | -############################################################################### |
| 106 | +# -------------------- Pre-checks -------------------- |
| 107 | +if (-not (Test-Path $GLPIAgentMSI)) { Log-Message "MSI not found: $GLPIAgentMSI" 'ERROR'; exit 1 } |
159 | 108 |
|
160 | | -# Always perform a clean installation: uninstall any existing version |
161 | | -Uninstall-GLPIAgent |
| 109 | +# Installed version? |
| 110 | +$installed = Get-InstalledGLPIInfo | Select-Object -First 1 |
| 111 | +if ($installed) { |
| 112 | + Log-Message "Detected installed GLPI Agent: '$($installed.DisplayName)' version '$($installed.DisplayVersion)'." |
| 113 | +} else { |
| 114 | + # Try fallback by file version |
| 115 | + $exe = Get-GLPIAgentPath |
| 116 | + if ($exe) { |
| 117 | + try { |
| 118 | + $fv = (Get-Item $exe).VersionInfo.ProductVersion |
| 119 | + if ($fv) { |
| 120 | + $installed = [PSCustomObject]@{ DisplayName='GLPI Agent (file)'; DisplayVersion=$fv; UninstallString=$null; InstallLocation=(Split-Path $exe -Parent); RegistryKey='(file)' } |
| 121 | + Log-Message "Detected GLPI Agent by executable: '$exe' (file version: $fv)." |
| 122 | + } |
| 123 | + } catch {} |
| 124 | + } |
| 125 | +} |
162 | 126 |
|
163 | | -# Install the GLPI Agent (even if no previous version exists) |
164 | | -Install-GLPIAgent -InstallerPath $GLPIAgentMSI |
| 127 | +# -------------------- Idempotent decision -------------------- |
| 128 | +$needInstall = $true |
| 129 | +if ($installed -and $installed.DisplayVersion) { |
| 130 | + if ($installed.DisplayVersion -like "$ExpectedVersion*") { |
| 131 | + $needInstall = $false |
| 132 | + Log-Message "Same version '$ExpectedVersion' already installed; skipping installation (idempotent)." |
| 133 | + } else { |
| 134 | + Log-Message "Installed version '$($installed.DisplayVersion)' differs from target '$ExpectedVersion'; will run MSI." |
| 135 | + } |
| 136 | +} else { |
| 137 | + Log-Message "GLPI Agent not found; will run MSI." |
| 138 | +} |
165 | 139 |
|
166 | | -# Try to locate the installed executable |
167 | | -$glpiAgentPath = Get-GLPIAgentPath |
168 | | -if (-not $glpiAgentPath) { |
169 | | - Log-Message "Error: Could not locate the GLPI Agent executable after installation." -Severity "WARNING" |
| 140 | +# -------------------- Install (no uninstall) -------------------- |
| 141 | +if ($needInstall) { |
| 142 | + # Build MSI arguments. We log MSI to a separate file for troubleshooting. |
| 143 | + $msiLog = Join-Path $GLPILogDir 'glpi-install.log' |
| 144 | + $installArgs = @( |
| 145 | + '/i', "`"$GLPIAgentMSI`"", |
| 146 | + '/qn', '/norestart', 'REBOOT=ReallySuppress', |
| 147 | + '/l*v', "`"$msiLog`"", |
| 148 | + "RUNNOW=1", |
| 149 | + "SERVER=`"$ServerUrl`"", |
| 150 | + "TAG=`"$domainTag`"" |
| 151 | + ) -join ' ' |
| 152 | + |
| 153 | + Log-Message "Executing: msiexec.exe $installArgs" |
| 154 | + |
| 155 | + try { |
| 156 | + # IMPORTANT: Do not block in GPO Startup. We omit -NoNewWindow to avoid conflicts with -WindowStyle. |
| 157 | + $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList $installArgs ` |
| 158 | + -WindowStyle Hidden -PassThru -Wait:(!$IsGpoStartup) -ErrorAction Stop |
| 159 | + |
| 160 | + # If we did not wait (GPO Startup), $proc may be $null; log accordingly. |
| 161 | + if ($proc) { |
| 162 | + Log-Message "msiexec exited with code: $($proc.ExitCode)" |
| 163 | + if ($proc.ExitCode -ne 0) { Log-Message "MSI returned non-zero exit code. See $msiLog" 'WARNING' } |
| 164 | + } else { |
| 165 | + Log-Message "Started msiexec in background (GPO Startup); see $msiLog for progress." |
| 166 | + } |
| 167 | + } |
| 168 | + catch { |
| 169 | + Log-Message "Failed to launch msiexec. Error: $_" 'ERROR' |
170 | 170 | exit 1 |
| 171 | + } |
171 | 172 | } else { |
172 | | - Log-Message "GLPI Agent found at: $glpiAgentPath" |
173 | | - # Apply the agent configuration |
174 | | - Configure-GLPIAgent -AgentPath $glpiAgentPath |
| 173 | + # Optionally ensure service is running, but do not block. |
| 174 | + try { |
| 175 | + $svc = Get-Service -ErrorAction SilentlyContinue | Where-Object { |
| 176 | + $_.Name -like '*glpi*agent*' -or $_.DisplayName -like '*GLPI*Agent*' |
| 177 | + } | Select-Object -First 1 |
| 178 | + if ($svc -and $svc.Status -ne 'Running') { |
| 179 | + Start-Service -Name $svc.Name -ErrorAction SilentlyContinue |
| 180 | + Log-Message "Started service '$($svc.Name)'." |
| 181 | + } |
| 182 | + } catch {} |
175 | 183 | } |
176 | 184 |
|
| 185 | +# -------------------- End -------------------- |
177 | 186 | Log-Message "End of script." |
178 | 187 | exit 0 |
179 | 188 |
|
|
0 commit comments