commit 889206ca034a4a3762e2c2656f79239cf7aa2a37 Author: Mathew Date: Thu Sep 19 14:01:18 2024 +0000 Initial commit version diff --git a/Install-PromtailOnWindows.ps1 b/Install-PromtailOnWindows.ps1 new file mode 100644 index 0000000..a6862ce --- /dev/null +++ b/Install-PromtailOnWindows.ps1 @@ -0,0 +1,440 @@ +############################################################################################# +# This script downloads, if necessary, Promtail and its WinSW (Windows Service wrapper), +# creates default configuration and creates Windows service. +# It's a decision based script. +# +# ↓ ↓ ↓ ↓ HELPER FUNCTIONS ↓ ↓ ↓ ↓ +# ↓↓↓↓↓↓↓ PROCESSING CODE ↓↓↓↓↓↓↓ +############################################################################################# + +param( + ## https://devblogs.microsoft.com/powershell/v2-custom-enums/ better, but limited to alphanumeric, so *DHCP would not work + [Parameter(Mandatory=$true, HelpMessage="Hotel site e.g. gb-bw-peartree-manchester, bikes, gb-ihg-holidayinn-birmingham")][String]$HotelGroup, + [Parameter(Mandatory=$true, HelpMessage="Hotel site e.g. operaapp, checkin, ")][ValidateSet('operaapp','checkin','hopapp')][String]$apptype, + [Parameter(Mandatory=$true, HelpMessage="Have you updated the following Group Policy? Computerconfig > windows settings > security settings > advanced audit policy config > object access > double click audit removable storage.(yes/no)")][ValidateSet('yes')][String]$gpochange + ) + +function Prompt-User { + param( + [string]$Prompt, + [object]$Default + ) + + if (-not [string]::IsNullOrEmpty($Default)) { + if ($Default -is [bool]) { + $Prompt += " [$(if ($Default) {'True'} else {'False'})]" + } + else { + $Prompt += " [$Default]" + } + } + + $input = Read-Host -Prompt $Prompt + + if ([string]::IsNullOrEmpty($input)) { + $input = $Default + } + else { + if ($Default -is [bool]) { + $input = [bool]::Parse($input) + } + elseif ($Default -is [int]) { + $input = [int]::Parse($input) + } + elseif ($Default -is [string]) { + # No conversion needed for string + } + else { + throw "Unsupported default value type: $($Default.GetType().FullName)" + } + + if ($input.GetType() -ne $Default.GetType()) { + throw "Entered value type doesn't match default value type" + } + } + + return $input +} + +function Ensure-Directory { + param( + [string]$Path + ) + + if (-not [System.IO.Directory]::Exists($Path)) { + [System.IO.Directory]::CreateDirectory($Path) | Out-Null + } +} + +function Download-File { + param ( + # Url of file to download + [Parameter(Mandatory)] + [string]$URL, + + # Parameter help description + [Parameter(Mandatory)] + [string]$File + ) + Begin { + function Show-Progress { + param ( + # Enter total value + [Parameter(Mandatory)] + [Single]$TotalValue, + + # Enter current value + [Parameter(Mandatory)] + [Single]$CurrentValue, + + # Enter custom progresstext + [Parameter(Mandatory)] + [string]$ProgressText, + + # Enter value suffix + [Parameter()] + [string]$ValueSuffix, + + # Enter bar lengh suffix + [Parameter()] + [int]$BarSize = 40, + + # show complete bar + [Parameter()] + [switch]$Complete + ) + + # calc % + $percent = $CurrentValue / $TotalValue + $percentComplete = $percent * 100 + if ($ValueSuffix) { + $ValueSuffix = " $ValueSuffix" # add space in front + } + if ($psISE) { + Write-Progress "$ProgressText $CurrentValue$ValueSuffix of $TotalValue$ValueSuffix" -id 0 -percentComplete $percentComplete + } + else { + # build progressbar with string function + $curBarSize = $BarSize * $percent + $progbar = "" + $progbar = $progbar.PadRight($curBarSize, [char]9608) + $progbar = $progbar.PadRight($BarSize, [char]9617) + + if (!$Complete.IsPresent) { + Write-Host -NoNewLine "`r$ProgressText $progbar [ $($CurrentValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete" + } + else { + Write-Host -NoNewLine "`r$ProgressText $progbar [ $($TotalValue.ToString("#.###").PadLeft($TotalValue.ToString("#.###").Length))$ValueSuffix / $($TotalValue.ToString("#.###"))$ValueSuffix ] $($percentComplete.ToString("##0.00").PadLeft(6)) % complete" + } + } + } + } + Process { + try { + $storeEAP = $ErrorActionPreference + $ErrorActionPreference = 'Stop' + + # invoke request + $request = [System.Net.HttpWebRequest]::Create($URL) + $response = $request.GetResponse() + + if ($response.StatusCode -eq 401 -or $response.StatusCode -eq 403 -or $response.StatusCode -eq 404) { + throw "Remote file either doesn't exist, is unauthorized, or is forbidden for '$URL'." + } + + if ($File -match '^\.\\') { + $File = Join-Path (Get-Location -PSProvider "FileSystem") ($File -Split '^\.')[1] + } + + if ($File -and !(Split-Path $File)) { + $File = Join-Path (Get-Location -PSProvider "FileSystem") $File + } + + if ($File) { + $fileDirectory = $([System.IO.Path]::GetDirectoryName($File)) + if (!(Test-Path($fileDirectory))) { + [System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null + } + } + + [long]$fullSize = $response.ContentLength + $fullSizeMB = $fullSize / 1024 / 1024 + + # define buffer + [byte[]]$buffer = new-object byte[] 1048576 + [long]$total = [long]$count = 0 + + # create reader / writer + $reader = $response.GetResponseStream() + $writer = new-object System.IO.FileStream $File, "Create" + + # start download + $finalBarCount = 0 #show final bar only one time + do { + + $count = $reader.Read($buffer, 0, $buffer.Length) + + $writer.Write($buffer, 0, $count) + + $total += $count + $totalMB = $total / 1024 / 1024 + + if ($fullSize -gt 0) { + Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB" + } + + if ($total -eq $fullSize -and $count -eq 0 -and $finalBarCount -eq 0) { + Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix "MB" -Complete + $finalBarCount++ + #Write-Host "$finalBarCount" + } + + } while ($count -gt 0) + } + + catch { + + $ExeptionMsg = $_.Exception.Message + Write-Host "Download breaks with error : $ExeptionMsg" + } + + finally { + # cleanup + if ($reader) { $reader.Close() } + if ($writer) { $writer.Flush(); $writer.Close() } + + $ErrorActionPreference = $storeEAP + [GC]::Collect() + + Write-Host + } + } +} + +function New-DefaultConfig { + param ( + # Parameter help description + [Parameter(Mandatory)] + [string]$fullConfigPath, + [Parameter(Mandatory)] + [string]$runPath + ) + + Write-Output "Writing default config to $fullConfigPath" + + $positionsFullpath = $runPath + "\positions.yaml" + + $content = " +# 1. Update positions.yaml path +# 2. Update client's url - this is the url of Loki service - update or remove basic_auth +# 3. Update what logs should be scraped + + +server: + http_listen_port: 9081 + grpc_listen_port: 0 + +positions: + filename: 'C:\ProgramData\Promtail\positions.txt' + +clients: + - url: 'https://loki.futuresens.co.uk/loki/api/v1/push' + +scrape_configs: + - job_name: testapptype + static_configs: + - targets: + - localhost + labels: + hotel: testhotel + job: testapptype + __path__: 'C:\Program Files\Self-Service Technology\Logs\testhotel.log' + - job_name: sst + static_configs: + - targets: + - localhost + labels: + hotel: testhotel + job: sst + __path__: 'C:\Program Files\Self-Service Technology\Logs\SST.Browser.log' + - job_name: windows-system + windows_events: + eventlog_name: 'Microsoft-Windows-DriverFrameworks-UserMode/Operational' + labels: + logsource: windows-eventlog + use_incoming_timestamp: true + bookmark_path: 'C:\ProgramData\Promtail\bookmark-system.xml' + exclude_event_data: true + exclude_user_data: true + locale: 1033 +" + $content = $content.Replace('testhotel',"$HotelGroup") + $content = $content.Replace('testapptype',"$apptype") + Set-Content -Path $fullConfigPath -Value $content +} + +function New-DefaultWinSWConfig { + param ( + # Parameter help description + [Parameter(Mandatory)] + [string]$fullConfigPath, + [Parameter(Mandatory)] + [string]$fullPromtailBinPath, + [Parameter(Mandatory)] + [string]$fullPromtailConfigPath, + [Parameter(Mandatory)] + [string]$serviceName, + [Parameter(Mandatory)] + [string]$serviceDisplayName + ) + + Write-Output "Writing default WinSW config to $fullConfigPath" + + $content = " + + + + + $serviceName + + $serviceDisplayName + + Starts a local Promtail service and scrapes logs according to configuration file: $fullPromtailConfigPath + + + ""$fullPromtailBinPath"" + + --config.file=""$fullPromtailConfigPath"" + + +" + + Set-Content -Path $fullConfigPath -Value $content +} + + +############################################# +# ↑ ↑ ↑ ↑ HELPER FUNCTIONS ↑ ↑ ↑ ↑ +# +# +# ↓ ↓ ↓ ↓ PROCESSING CODE ↓ ↓ ↓ ↓ +############################################# + + +Write-Warning -Message "This script creates a Window Service for Promtail log scraper. It is necessary to run it with Admin priviledges. + +It can download necessary files from the Internet, but you can also put already downloaded files directly to proper directories." + +## Below will enable event logs to allow the USB decetion logs. + +$logDetails = Get-LogProperties 'Microsoft-Windows-DriverFrameworks-UserMode/Operational' +$logDetails.Enabled = $True +Set-LogProperties -LogDetails $logDetails +Get-LogProperties 'Microsoft-Windows-DriverFrameworks-UserMode/Operational' + +$downloadUrl = "https://github.com/grafana/loki/releases/download/v2.9.5/promtail-windows-amd64.exe.zip" +$downloadWinSWUrl = "https://github.com/winsw/winsw/releases/download/v2.12.0/WinSW-x64.exe" +$winSWFilename = "WinSW-x64.exe" +$binFilename = "promtail-windows-amd64.exe" +$configFilename = "promtail.yml" +$winSWConfigFilename = "WinSW-x64.xml" + +$runPath = Prompt-User -Prompt "Run directory" -Default "C:\Promtail" +$fullBinPath = Join-Path -Path $runPath -ChildPath $binFilename +$fullWinSWBinPath = Join-Path -Path $runPath -ChildPath $winSWFilename +$downloadWinSWPath = $runPath + +$shouldDownloadPromtail = Prompt-User -Prompt "Should we download Promtail?" -Default $true + +if ($shouldDownloadPromtail) { + $downloadUrl = Prompt-User -Prompt "Download url" -Default $downloadUrl + $downloadPath = Prompt-User -Prompt "Download directory" -Default $runPath + + Ensure-Directory -Path $downloadPath + + $filename = $downloadUrl.Split("/")[-1] + $fullPath = Join-Path -Path $downloadPath -ChildPath $filename + + Write-Host "Downloading archive..." + Download-File -URL $downloadUrl -File $fullPath + + Write-Host "Expanding archive..." + Expand-Archive -LiteralPath $fullPath -DestinationPath $runPath -Force +} +else { + if (-not [System.IO.File]::Exists($fullBinPath)) { + throw "Could not find $fullBinPath" + } +} + +$shouldCreateConfig = Prompt-User -Prompt "Create default promtail.yml config?" -Default $true +$configFullpath = Join-Path -Path $runPath -ChildPath $configFilename + +if ($shouldCreateConfig) { + New-DefaultConfig -fullConfigPath $configFullpath -runPath $runPath +} +else { + $configFullpath = Prompt-User -Prompt "Promtail configuration file path" -Default $configFullpath + + if (-not [System.IO.File]::Exists($configFullpath)) { + throw "Could not find $configFullpath" + } +} + +$shouldCreateService = Prompt-User -Prompt "Create Promtail Windows service?" -Default $true + +if ($shouldCreateService) { + + $shouldDownloadWinSWUrl = Prompt-User -Prompt "Should we download Windows Service wrapper (WinSWUrl)?" -Default $true + + if ($shouldDownloadWinSWUrl) { + $downloadUrl = Prompt-User -Prompt "Download url" -Default $downloadWinSWUrl + $downloadWinSWPath = Prompt-User -Prompt "Download directory" -Default $runPath + + Ensure-Directory -Path $downloadPath + + $filename = $winSWFilename + $fullWinSWBinPath = Join-Path -Path $downloadWinSWPath -ChildPath $filename + + Write-Host "Downloading WinSW exe file..." + Download-File -URL $downloadUrl -File $fullWinSWBinPath + } + else { + if (-not [System.IO.File]::Exists($fullWinSWBinPath)) { + throw "Could not find $fullWinSWBinPath" + } + } + + + $winSWconfigFullpath = Join-Path -Path $downloadWinSWPath -ChildPath $winSWConfigFilename + $shouldCreateWinSWConfig = Prompt-User -Prompt "Create WinSW config as $winSWconfigFullpath ?" -Default $true + + if ($shouldCreateWinSWConfig) { + $serviceName = Prompt-User -Prompt "Service name" -Default "Promtail" + $serviceDisplayName = Prompt-User -Prompt "Service name" -Default "Promtail Logs scraper" + + New-DefaultWinSWConfig -fullConfigPath $winSWconfigFullpath -fullPromtailBinPath $fullBinPath -fullPromtailConfigPath $configFullpath -serviceName $serviceName -serviceDisplayName $serviceDisplayName + } + else { + if (-not [System.IO.File]::Exists($winSWconfigFullpath)) { + throw "Could not find $winSWconfigFullpath" + } + } + + Write-Host "Installing Promtail Windows Service" + + Start-Process -FilePath $fullWinSWBinPath -ArgumentList @("install") -NoNewWindow + + Write-Host "Starting the Service" + + start-sleep -Seconds 5 + + Start-Service Promtail + + Set-ExecutionPolicy Restricted -Force + + Write-Host "Reverting execution policy" +} \ No newline at end of file