[Feature]: Bulk Windows Installer
What is the installation method of your PatchMon server?
Docker
What is the version of your PatchMon server?
2.0.2
Briefly describe the feature
Windows Auto Enroll/Bulk Install
Detailed description
Would love to see a way to deploy the agent to more than one Windows host at a time via ether some type of Auto Enroll system and generic install script. Or just be able to use the normal install script and not have to define a hostname and let the script pull it from the system, allowing you to use the script on multiple hosts at once.
Why is this useful?
Makes it easier to mass deploy PatchMon via other RRM tools. For example, I would like to use a single script in my TacticalRMM instance that will install the agent, and it will auto-enroll to PatchMon
2 Comments
I used something like this to make the computer record and install the agent without having to prepopulate the record. I also needed mine to be digitally signed in my case, having a static file that copied the bootstrap token made that possible to sign easily.
I may make changes, this is my first draft of it. It is working though.
You'll need to create an autoenrollment token and put it in the $autoEnroll* variables.
I'm distributing this through GPO, creates a scheduled task that runs every day. This way as new computers get added to the domain they also get the task and install the agent.
#https://patchmon.net/docs/patchmon-operator-guide#installing-the-patchmon-agent
#a lot of this was taken from the installer script you download from the patchmon server that embeds the bootstrap token, but I needed to have a signed version, so I did some of it differently.
$ServerURL = "https://patchmon.host.edu"
$InstallPath = "C:\Program Files\PatchMon"
$ConfigPath = "C:\ProgramData\PatchMon"
$SkipSslVerify = $false
$serviceName = "PatchMonAgent"
$serviceDisplayName = "PatchMon Agent"
$serviceDescription = "PatchMon Agent - Monitors system packages and sends updates to PatchMon server"
$autoEnrollKey="patchmon_ae_abcde"
$autoEnrollSecret+"abcdef"
function log( $message){
try{
$message = "<$(get-date -Format "MM/dd/yy hh:mm")" + $message
$logFile = $("C:\patchmon_" + $(get-date -uformat "%Y_%m") + ".log")
#write-host $message |out-file $logFile -append
add-content $logFile $($message) |Out-Null
#write-host $thisScript
} catch [Exception]{
Write-host $("Log Error: " + $_.Exception.Message);
}
}
$apiId = $null
$apiKey = $null
# Check if service already exists
$existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($existingService) {
Write-Host "Service already exists, no need to install. I think?" -ForegroundColor Yellow
exit 0
}
#Make new host in patchmon
#get initial api_id andn key
try{
$enroll=Invoke-WebRequest -Uri "$ServerURL/api/v1/auto-enrollment/enroll" -Headers @{"X-Auto-Enrollment-Key"=$($autoEnrollKey); "X-Auto-Enrollment-Secret"=$($autoEnrollSecret)} -Method POST -Body "{`"friendly_name`":`"$($env:COMPUTERNAME)`"}" -ContentType "application/json" -UseBasicParsing
if($enroll.StatusCode -eq 201){
$e=convertfrom-json $enroll
$apiId = $e.host.api_id
$apiKey = $e.host.api_key
log "Enrollment Successful"
}else{
log "Failed enrollment code:$($enroll.StatusCode)"
}
}catch [Exception]{
log "Failed enrollment code:$($enroll.StatusCode)"
}
#get install ps1 - will only be taking booststrap api key out of it - don't want to bother with singing this at install time - too much work and don't want to have a signing cert on all servers
try{
$r = Invoke-WebRequest -Uri "https://patchmon.sys.utahtech.edu/api/v1/hosts/install?os=windows" -Headers @{"X-API-ID"="$apiId"; "X-API-KEY"="$apiKey"} -UseBasicParsing; $r.Content | Set-Content "$env:TEMP\patchmon-install.ps1" -Encoding UTF8;
$BootstrapToken = (((get-content $env:TEMP\patchmon-install.ps1|select-string 'env:PATCHMON_BOOTSTRAP_TOKEN =') -split " = ")[1] -replace '"', '')
}catch [Exception]{
log "Failed to download installer code:$($enroll.StatusCode)"
}
Write-Host "Fetching credentials from PatchMon server..." -ForegroundColor Cyan
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$body = @{ token = $BootstrapToken } | ConvertTo-Json
$response = Invoke-RestMethod -Uri "$ServerURL/api/v1/hosts/bootstrap/exchange" -Method Post -Body $body -ContentType "application/json" -UseBasicParsing
#permenant api id and key
$APIID = $apiId
$APIKey = $apiKey
if (-not $APIID -or -not $APIKey) {
log "Failed to fetch credentials. Bootstrap token may have expired. Please request a new installation script."
Write-Error "Failed to fetch credentials. Bootstrap token may have expired. Please request a new installation script."
exit 1
}
Write-Host "Credentials received successfully." -ForegroundColor Green
}catch {
log "Failed to fetch credentials: $($_.Exception.Message). Bootstrap token may have expired. Please request a new installation script."
Write-Error "Failed to fetch credentials: $($_.Exception.Message). Bootstrap token may have expired. Please request a new installation script."
exit 1
}
# Download the binary from the server
$downloadURL = "$ServerURL/api/v1/hosts/agent/download?arch=amd64&os=windows"
Write-Host "Downloading PatchMon agent..." -ForegroundColor Yellow
try {
$headers = @{}
if ($APIID -and $APIKey) {
$headers["X-API-ID"] = $APIID
$headers["X-API-KEY"] = $APIKey
}
Invoke-WebRequest -Uri $downloadURL -
Followed this up and got the script above working as a full GPO deployment on a Windows domain - staged to SYSVOL, installed by a GPO scheduled-task preference running as SYSTEM, hosts self-enrol through auto-enrollment so no pre-created host records are needed. Ran it manually on a test client, then deployed it via the GPO task and the host enrolled and reported in cleanly.
Repo with everything (installer, uninstaller, the scheduled-task XML, the GPO staging helper, runbook): https://github.com/Teejer/patchmon-agent-deploy
Things the draft script needed that I'd flag for anyone else picking this up:
config.ymlmust use single-quoted Windows paths.credentials_file: "C:\ProgramData\PatchMon\credentials.yml"is invalid YAML - a double-quoted scalar processes backslash escapes, so\cis an unknown escape. The nasty part is the agent's reaction: it warns once, replaces the file with its own defaults (nopatchmon_server) and carries on, so the host silently never reports. PatchMon's ownpatchmon_install_windows.ps1writes single-quoted scalars for exactly this reason; this now matches it, and repairs files written the broken way.$autoEnrollSecret+"abcdef"(concatenation instead of assignment), a hardcoded server URL, and$tempPathused before it was assigned with the download block duplicated.New-Service -Descriptiondoesn't exist there (usedsc.exe description), and-SkipCertificateCheckis PS 6+ ([System.Net.ServicePointManager]callback instead).config.ymlis parseable and points at the configured server - otherwise it repairs the config, restarts the service and confirms withpatchmon-agent pingbefore considering a reinstall. The service is stopped before its binary is replaced and started rather than recreated when it exists, so the task can run daily without breaking anything.One deployment caveat worth knowing: the staged script in SYSVOL is readable by every authenticated domain user and contains the auto-enrollment credentials, so treat that token as shareable-to-the-domain and rotate it independently of the admin API credentials.
The installer as deployed (server URL and enrollment credentials are placeholders here):