PowerShell: Install Windows Exporter

Installing Prometheus Windows Exporter Client on a List of Windows Servers

# installWindowsExporter.ps1
# version: 0.0.2
# Limitations: this script assumes that the Windows machine has outbound access to github (firewall allowed)
# Current iteration is sequential - next iteration is to run installs in parallel

# User input-variables
$computerNames=@(
    'SERVER001',
    'SERVER002'
)

$parentUrl='https://github.com/prometheus-community/windows_exporter/releases'
$fileExtension='.msi'
$maxDepth=1
$serviceName='windows_exporter'
$resets=30
$restartWaitMs=100000
$maxWaitSeconds=120

function installWindowsExporter{
    param(
        $computernames=$env:computername,
        $parentUrl='https://github.com/prometheus-community/windows_exporter/releases',
        $fileExtension='.msi',
        $maxDepth=1,
        $serviceName='windows_exporter',
        $resets=30,
        $restartWaitMs=100000,
        $searchTimeout=30,
        $maxWaitSeconds=120
    )
    function installWindowsExporterUsingChoco{
        param(
            $parentUrl='https://github.com/prometheus-community/windows_exporter/releases',
            $fileExtension='.msi',
            $maxDepth=1,
            $serviceName='windows_exporter',
            $resets=30,
            $restartWaitMs=100000,
            $searchTimeout=30
        )
    
        # Check whether product is installed before proceeding
        function checkUninstall($serviceName){
            $cpuArchitecture32bitPointerSize=4
            $path=if ([IntPtr]::Size -eq $cpuArchitecture32bitPointerSize) {
                'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
            }else{
                @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
                  'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
            }
            Get-ItemProperty $path |.{process{ if ($_.DisplayName -eq $serviceName -and $_.UninstallString) { $_ } }} |
            Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString
        }
        $installed=checkUninstall $serviceName
    
        if(!$installed){
            try{
                # Install Chocolatey
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                    Set-ExecutionPolicy Bypass -Scope Process -Force;
                    $null=iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}
                $null=choco install prometheus-windows-exporter.install -y --ignore-checksums
            }catch{
                write-warning $_
                return $false
            }
        }else{
            write-host "$env:computername already has $servicename installed"
        }
        
        try{
            # Set auto start and restart upon failures
            $null=& sc.exe failure $serviceName reset= $resets actions= restart/$restartWaitMs/restart/$restartWaitMs/""/$($restartWaitMs*3)
            Set-Service -Name $serviceName -StartupType 'Automatic'
            write-host "$env:computername now has $servicename set to automatically run and reset at $resets and restart wait-time of $restartWaitMs ms."
            return $(get-service $serviceName).Status -eq 'Running'
        }catch{
            write-warning $_
            return $false
        }
    }

    $results=@()
    $computernames|%{
    $job=invoke-command -computername $_ -scriptblock{
    param($installWindowsExporterUsingChoco,$parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs)
    [ScriptBlock]::Create($installWindowsExporterUsingChoco).invoke($parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs)
    } -ArgumentList ${function:installWindowsExporterUsingChoco},$parentUrl,$fileExtension,$maxDepth,$serviceName,$resets,$restartWaitMs -AsJob -JobName installPrometheusExporter
    $count=0
    while (($job.State -like "Running") -and ($count -lt $maxWaitSeconds)){
        Start-Sleep -Seconds 1
        $count++
    }
    if ($Job.State -like "Running") { $job | Stop-Job }
    $success=$job | Receive-Job
    $job|Remove-Job
    $result=if($success){
        [pscustomobject]@{$_=$success}
    }else{
        [pscustomobject]@{$_=$false}
    }
    $results+=$result
    }
    return $results
}

$status=installWindowsExporter $computerNames $parentUrl $fileExtension $maxDepth $serviceName $resets $restartWaitMs $maxWaitSeconds
write-host $status

Previous Version: Install Using Github

# installWindowsExporter.ps1
# version: 0.0.1
# Limitations: this script assumes that the Windows machine has outbound access to github (firewall allowed)

# User input-variables
$parentUrl='https://github.com/prometheus-community/windows_exporter/releases'
$fileExtension='.msi'
$maxDepth=1
 
function findDownloadUrl{
    param(
        $startUrl,
        $fileExtension,
        $maxDepth=3
    )
    $simultaneousJobs=8
    $linksChecked=0
    $firstResult=$false
    $timer=[System.Diagnostics.Stopwatch]::StartNew()
    if(!$startUrl){
        write-warning "Cannot start with a blank parent URL"
    }elseif($startUrl -notmatch '/$'){
        $startUrl=$startUrl+'/'
        }
 
    function findFile($parentUrl,$extension){
        $ProgressPreference='SilentlyContinue'
        $ErrorActionPreference='stop'
        if($parentUrl -notmatch '/$'){$parentUrl=$parentUrl+'/'}
        try{
            $page=Invoke-WebRequest $parentUrl -TimeoutSec 10
        }catch{
            return @{'result'=$false;'links'=@()}
            }
        $newLinks=$page.links.href|?{$_ -notlike "*$(Split-Path $parentUrl -parent)"}| `
            sort -Descending|%{$(
                                if($_[0] -eq '/'){
                                    $parentUrl+$_.Substring(1,$_.length-1)
                                }elseif($_ -match '^http'){
                                    $_
                                }else{
                                    $parentUrl+$_
                                }
                            )}|select -Unique
        $matchedExtension=$newLinks|?{$_ -like "*$extension"}|sort -Descending|select -First 1
        if($matchedExtension){
            return @{'result'=$true;'links'=$matchedExtension}
        }elseif($newLinks){
            return @{'result'=$false;'links'=$newLinks}
        }else{
            return @{'result'=$false;'links'=@()}
            } 
    }  
 
    write-host "Scanning $startUrl for file extension $fileExtension"
    $startLinks=.{$result=findFile $startUrl $fileExtension
                    return $result['links']
                    }    
    if($startLinks -eq $null){
        write-warning "There were problems parsing links"
        return $null
    }elseif($startLinks.gettype() -eq [string]){
        return $startLinks
    }
    $knownLinks=$startLinks
 
    foreach ($link in $startLinks){       
        $currentDepth=1
        write-host "Processing link at current depth: $currentDepth"
        $newLinks=@($link) 
        do{ 
            if($i++ -lt $simultaneousJobs -and !(!$newLinks)){
                $thisLink=$newLinks|Select -Unique|select -First 1
                if($newLinks.count -gt 1){
                    $newLinks=$newLinks[1..($newLinks.count-1)]
                }else{
                    $newLinks=@()
                    }
                write-host "Parsing $thisLink"
                $job=start-job -ScriptBlock{
                    param($findFile,$thisLink,$fileExtension)
                    return [ScriptBlock]::Create($findFile).invoke($thisLink,$fileExtension)
                    } -Args ${function:findFile},$thisLink,$fileExtension
                $linksChecked++
            }else{
                do{
                    $results=Get-Job|Receive-Job -wait
                    get-job -State 'Completed'|remove-job                    
                    $results|%{
                        $currentDepth++
                        if($_['result']){
                            write-host "Bingo!" -ForegroundColor Green
                            get-job|remove-job
                            $firstResult=$_['links']
                        }elseif($currentDepth -le $maxDepth){
                            $addLinks=$_['links']|?{$_ -notin $knownLinks}
                            if($addLinks){
                                write-host "Adding new links to depth $currentDepth`:`r`n$(($addLinks|out-string).trim())"
                                $knownLinks+=$addLinks
                                $newLinks=$addLinks+$newLinks
                                }
                            }
                        }
                    $i=(get-job -state 'Running').count
                    }until($i -lt $simultaneousJobs -or $firstResult) 
                }
        }until((!$newLinks -and !$i) -or $firstResult)            
                
        if($firstResult){
            $totalMinutes=[math]::round($timer.Elapsed.TotalMinutes,2)
            write-host "Minutes elapsed: $totalMinutes"
            return $firstResult
            }
    }
 
    $totalMinutes=[math]::round($timer.Elapsed.TotalMinutes,2)
    write-host "$linksChecked links have been checked in $totalMinutes minutes without finding file extension $fileExtension" -ForegroundColor Red
    return $false
}
$windowsExporterUrl=findDownloadUrl $parentUrl $fileExtension $maxDepth
# $fileName=[regex]::match($windowsExporterUrl,'(?:.(?!\/))+$') # this negative lookahead includes the '/' slashes
$fileName=[regex]::match($windowsExporterUrl,'[^/\\&\?]+\.\w{3,4}(?=([\?&].*$|$))')

# $windowsExporterUrl='https://github.com/prometheus-community/windows_exporter/releases/download/v0.18.1/windows_exporter-0.18.1-amd64.msi'
# $fileName='windows_exporter-0.18.1-amd64.msi'
$stageFolder='C:\Temp\'
$msiFile=join-path $stageFolder $fileName

# Faster way of installing if there are no firewall problems
# choco install prometheus-windows-exporter.install

# Download the file
Import-Module BitsTransfer
if(!(test-path $stageFolder)){mkdir $stageFolder}
Start-BitsTransfer -Source $windowsExporterUrl -Destination $msiFile

# Install using MSIEXEC
msiexec /i $msiFile ENABLED_COLLECTORS=os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile /quiet

# Check whether product is installed
$serviceName='windows_exporter'
function checkUninstall($serviceName){
    $cpuArchitecture32bitPointerSize=4
    $path=if ([IntPtr]::Size -eq $cpuArchitecture32bitPointerSize) {
        'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
    }else{
        @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
          'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
    }
    Get-ItemProperty $path |.{process{ if ($_.DisplayName -eq $serviceName -and $_.UninstallString) { $_ } }} |
    Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString
}
checkUninstall $serviceName

# Set auto start and restart upon failures
$serviceName='windows_exporter'
& sc.exe failure $serviceName reset= 30 actions= restart/100000/restart/100000/""/300000
Set-Service -Name $serviceName -StartupType 'Automatic'

Leave a Reply

Your email address will not be published. Required fields are marked *