############################################################################################# # 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="Kiosk Number e.g. 1, 2, 3, 4 (use the numeric) ")][String]$kiosknumber, [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 kiosk: testkiosknumber __path__: 'C:\Program Files\Self-Service Technology\Logs\testhotel.log' - job_name: sst static_configs: - targets: - localhost labels: hotel: testhotel job: sst kiosk: testkiosknumber __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 hotel: gb-i-peartree-salisbury job: winlogs kiosk: testkiosknumber 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") $content = $content.Replace('testkiosknumber',"$kiosknumber") 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' ## The below line will update the group Policy to enable the removeable storage audit, if GP edit exists if (Test-Path 'C:\Windows\System32\gpedit.msc') { Write-Host 'Enabling Group Policy - Audit removeable storage.' auditpol /set /subcategory:"Removable Storage" /success:enable /failure:enable } else{ Write-Host 'c:\Windows\System32\gpedit.msc does NOT exist, Exiting on safety grounds' exit } $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 Ensure-Directory -Path 'c:\ProgramData\Promtail' ## Now create the positions files. NB Please create a Positions file for each Job. New-Item -Path 'c:\ProgramData\Promtail\positions.txt' -ItemType File New-Item -Path 'c:\ProgramData\Promtail\bookmark-system.xml' -ItemType File $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" } } gpupdate /force 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" }