Deployment_scripts/Install-PromtailOnWindows.ps1

446 lines
15 KiB
PowerShell

#############################################################################################
# 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 = "
<!--
You can find more information about the configuration options here: https://github.com/kohsuke/winsw/blob/master/doc/xmlConfigFile.md
Full example: https://github.com/kohsuke/winsw/blob/master/examples/sample-allOptions.xml
-->
<service>
<!-- ID of the service. It should be unique across the Windows system-->
<id>$serviceName</id>
<!-- Display name of the service -->
<name>$serviceDisplayName</name>
<!-- Service description -->
<description>Starts a local Promtail service and scrapes logs according to configuration file: $fullPromtailConfigPath</description>
<!-- Path to the executable, which should be started -->
<executable>""$fullPromtailBinPath""</executable>
<arguments>--config.file=""$fullPromtailConfigPath""</arguments>
</service>
"
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
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"
}
}
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"
}