Feature Requests
New / Open / Unreviewed

[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

1 Comment

Posting anonymously

Teejer·3 months ago

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 -

Posting anonymously