Skip to content

Commit

Permalink
Merge pull request #688 from Icinga:feature/adds_scheduled_task_offlo…
Browse files Browse the repository at this point in the history
…ader

Feature: Adds handling to add scheduled task background task

Adds new handling to add scheduled tasks in Windows for interacting with Icinga for Windows core functionality as well as an auto renewal task for the Icinga for Windows certificate generation
  • Loading branch information
LordHepipud authored Mar 12, 2024
2 parents cd0f8c1 + ed3e8dc commit 959fac6
Show file tree
Hide file tree
Showing 15 changed files with 369 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/100-General/10-Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
### Enhancements

* [#679](https://github.com/Icinga/icinga-powershell-framework/pull/679) Adds a new data provider for fetching process information of Windows systems, while sorting all objects based on a process name and their process id
* [#688](https://github.com/Icinga/icinga-powershell-framework/pull/688) Adds new handling to add scheduled tasks in Windows for interacting with Icinga for Windows core functionality as well as an auto renewal task for the Icinga for Windows certificate generation

## 1.11.2 (tbd)

Expand Down
32 changes: 32 additions & 0 deletions jobs/GetWindowsService.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
param (
[string]$ServiceName = '',
[string]$TmpFilePath = ''
);

Use-Icinga -Minimal;

[string]$ErrMsg = "";
[hashtable]$ServiceData = @{
'Status' = '';
'Present' = $FALSE;
'Name' = 'Unknown';
'DisplayName' = 'Unknown';
};

try {
$SvcData = Get-Service "$ServiceName" -ErrorAction Stop;
$ServiceData.Status = [string]$SvcData.Status;
$ServiceData.Name = $SvcData.Name;
$ServiceData.DisplayName = $SvcData.DisplayName;
$ServiceData.Present = $TRUE;
} catch {
$ErrMsg = [string]::Format('Failed to get data for service "{0}": {1}', $ServiceName, $_.Exception.Message);
}

Write-IcingaFileSecure -File "$TmpFilePath" -Value (
@{
'Service' = $ServiceData;
'Message' = [string]::Format('Successfully fetched data for service "{0}"', $ServiceName);
'ErrMsg' = $ErrMsg;
} | ConvertTo-Json -Depth 100
);
33 changes: 33 additions & 0 deletions jobs/RenewCertificate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Use-Icinga -Minimal;

# This script will simply install the Icinga for Windows certificate everyime the
# scheduled task is running. This does not impact our system at all, because we
# can update the certificate at any time without having to worry about the state

# To make the configuration of the task as easy as possible, we should fetch
# the current configuration of our REST-Api and check if we provide a custom
# certificate file or thumbprint. In case we do, ensure we use this certificate
# for the icingaforwindows.pfx creation instead of the auto lookup
[hashtable]$RegisteredBackgroundDaemons = Get-IcingaBackgroundDaemons;
[string]$CertificatePath = '';
[string]$CertificateThumbprint = '';

if ($RegisteredBackgroundDaemons.ContainsKey('Start-IcingaWindowsRESTApi')) {
if ($RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi'].ContainsKey('CertFile')) {
$CertificatePath = $RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi']['CertFile'];
}
if ($RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi'].ContainsKey('-CertFile')) {
$CertificatePath = $RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi']['-CertFile'];
}
if ($RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi'].ContainsKey('CertThumbprint')) {
$CertificateThumbprint = $RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi']['CertThumbprint'];
}
if ($RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi'].ContainsKey('-CertThumbprint')) {
$CertificateThumbprint = $RegisteredBackgroundDaemons['Start-IcingaWindowsRESTApi']['-CertThumbprint'];
}
}

Install-IcingaForWindowsCertificate -CertFile $CertificatePath -CertThumbprint $CertificateThumbprint;

# Tell the Task-Scheduler that the script was executed fine
exit 0;
27 changes: 27 additions & 0 deletions jobs/RestartWindowsService.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
param (
[string]$ServiceName = '',
[string]$TmpFilePath = ''
);

Use-Icinga -Minimal;

[bool]$Success = $TRUE;
[string]$ErrMsg = "";
[string]$Status = '';

try {
Restart-Service "$ServiceName" -ErrorAction Stop;
$Status = [string](Get-Service "$ServiceName").Status;
} catch {
$Success = $FALSE;
$ErrMsg = [string]::Format('Failed to restart service "{0}": {1}', $ServiceName, $_.Exception.Message);
}

Write-IcingaFileSecure -File "$TmpFilePath" -Value (
@{
'Success' = $Success;
'Message' = [string]::Format('Service "{0}" successfully restarted', $ServiceName);
'ErrMsg' = $ErrMsg;
'Status' = $Status;
} | ConvertTo-Json -Depth 100
);
27 changes: 27 additions & 0 deletions jobs/StartWindowsService.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
param (
[string]$ServiceName = '',
[string]$TmpFilePath = ''
);

Use-Icinga -Minimal;

[bool]$Success = $TRUE;
[string]$ErrMsg = "";
[string]$Status = '';

try {
Start-Service "$ServiceName" -ErrorAction Stop;
$Status = [string](Get-Service "$ServiceName").Status;
} catch {
$Success = $FALSE;
$ErrMsg = [string]::Format('Failed to start service "{0}": {1}', $ServiceName, $_.Exception.Message);
}

Write-IcingaFileSecure -File "$TmpFilePath" -Value (
@{
'Success' = $Success;
'Message' = [string]::Format('Service "{0}" successfully started', $ServiceName);
'ErrMsg' = $ErrMsg;
'Status' = $Status;
} | ConvertTo-Json -Depth 100
);
27 changes: 27 additions & 0 deletions jobs/StopWindowsService.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
param (
[string]$ServiceName = '',
[string]$TmpFilePath = ''
);

Use-Icinga -Minimal;

[bool]$Success = $TRUE;
[string]$ErrMsg = "";
[string]$Status = '';

try {
Stop-Service "$ServiceName" -ErrorAction Stop;
$Status = [string](Get-Service "$ServiceName").Status;
} catch {
$Success = $FALSE;
$ErrMsg = [string]::Format('Failed to stop service "{0}": {1}', $ServiceName, $_.Exception.Message);
}

Write-IcingaFileSecure -File "$TmpFilePath" -Value (
@{
'Success' = $Success;
'Message' = [string]::Format('Service "{0}" successfully stopped', $ServiceName);
'ErrMsg' = $ErrMsg;
'Status' = $Status;
} | ConvertTo-Json -Depth 100
);
9 changes: 9 additions & 0 deletions lib/core/framework/Invoke-IcingaForWindowsMigration.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,13 @@ function Invoke-IcingaForWindowsMigration()

Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.10.1');
}

if (Test-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.12.0')) {
Write-IcingaConsoleNotice 'Applying pending migrations required for Icinga for Windows v1.12.0';

# Add a new scheduled task to automatically renew the Icinga for Windows certificate
Register-IcingaWindowsScheduledTaskRenewCertificate -Force;

Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.12.0');
}
}
2 changes: 2 additions & 0 deletions lib/core/framework/Uninstall-IcingaForWindows.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function Uninstall-IcingaForWindows()
Uninstall-IcingaSecurity -IcingaUser $IcingaUser;
Write-IcingaConsoleNotice 'Uninstalling Icinga Agent';
Uninstall-IcingaAgent -RemoveDataFolder | Out-Null;
Write-IcingaConsoleNotice 'Uninstalling Certificate Renewal Task';
Unregister-IcingaWindowsScheduledTaskRenewCertificate;
if ($ComponentsOnly -eq $FALSE) {
Write-IcingaConsoleNotice 'Uninstalling Icinga for Windows EventLog';
Unregister-IcingaEventLog;
Expand Down
3 changes: 3 additions & 0 deletions lib/core/installer/Start-IcingaForWindowsInstallation.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ function Start-IcingaForWindowsInstallation()
};
}

# Ensure we add the scheduled task to renew the certificates for Icinga for Windows on a daily basis
Register-IcingaWindowsScheduledTaskRenewCertificate -Force;

switch ($InstallJEAProfile) {
'0' {
Install-IcingaJEAProfile;
Expand Down
93 changes: 93 additions & 0 deletions lib/core/wintasks/Invoke-IcingaWindowsScheduledTask.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
function Invoke-IcingaWindowsScheduledTask()
{
param (
[ValidateSet('UninstallAgent', 'UpgradeAgent', 'ReadMSIPackage', 'InstallJEA', 'StartWindowsService', 'StopWindowsService', 'RestartWindowsService', 'GetWindowsService')]
[string]$JobType = '',
[string]$FilePath = '',
[string]$TargetPath = '',
[string]$ObjectName = ''
);

if ((Test-AdministrativeShell) -eq $FALSE) {
Write-IcingaConsoleError 'You require to run this shell in administrative mode for the action "{0}" and object "{1}"' -Objects $JobType, $ObjectName;
return $null;
}

[string]$TaskName = 'Management Task';
[string]$TaskPath = '\Icinga\Icinga for Windows\';
$TaskData = $null;
$TmpFile = New-IcingaTemporaryFile;

if (Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$FALSE -ErrorAction SilentlyContinue | Out-Null;
}

switch ($JobType) {
'StartWindowsService' {
$TaskData = Invoke-IcingaWindowsServiceHandlerTask -ScriptPath 'jobs\StartWindowsService.ps1' -ServiceName $ObjectName -TmpFile $TmpFile.FullName -TaskName $TaskName -TaskPath $TaskPath;
};
'StopWindowsService' {
$TaskData = Invoke-IcingaWindowsServiceHandlerTask -ScriptPath 'jobs\StopWindowsService.ps1' -ServiceName $ObjectName -TmpFile $TmpFile.FullName -TaskName $TaskName -TaskPath $TaskPath;
};
'RestartWindowsService' {
$TaskData = Invoke-IcingaWindowsServiceHandlerTask -ScriptPath 'jobs\RestartWindowsService.ps1' -ServiceName $ObjectName -TmpFile $TmpFile.FullName -TaskName $TaskName -TaskPath $TaskPath;
};
'GetWindowsService' {
$TaskData = Invoke-IcingaWindowsServiceHandlerTask -ScriptPath 'jobs\GetWindowsService.ps1' -ServiceName $ObjectName -TmpFile $TmpFile.FullName -TaskName $TaskName -TaskPath $TaskPath;
};
'UninstallAgent' {
$WinAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ([string]::Format('-WindowStyle Hidden -Command &{{ Use-Icinga -Minimal; Write-IcingaFileSecure -File {0}{1}{0} -Value (Start-IcingaProcess -Executable {0}MsiExec.exe{0} -Arguments {0}"{2}" /q{0} -FlushNewLines | ConvertTo-Json -Depth 100); }}', "'", $TmpFile.FullName, $FilePath, $TargetPath))
Register-ScheduledTask -TaskName $TaskName -Action $WinAction -RunLevel Highest -TaskPath $TaskPath | Out-Null;

Start-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;

Wait-IcingaWindowsScheduledTask;
# Wait some time before continuing to ensure the service is properly removed
Start-Sleep -Seconds 2;

[string]$TaskOutput = Read-IcingaFileSecure -File $TmpFile.FullName;
$TaskData = ConvertFrom-Json $TaskOutput;
};
'UpgradeAgent' {

};
'ReadMSIPackage' {
if (Test-Path $FilePath) {

$WinAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ([string]::Format('-WindowStyle Hidden -Command &{{ Use-Icinga -Minimal; Write-IcingaFileSecure -File {0}{1}{0} -Value (Read-IcingaMSIMetadata -File {0}{2}{0} | ConvertTo-Json -Depth 100); }}', "'", $TmpFile.FullName, $FilePath))
Register-ScheduledTask -TaskName $TaskName -Action $WinAction -RunLevel Highest -TaskPath $TaskPath | Out-Null;

Start-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;

Wait-IcingaWindowsScheduledTask;

[string]$TaskOutput = Read-IcingaFileSecure -File $TmpFile.FullName;
$TaskData = ConvertFrom-Json $TaskOutput;
} else {
Write-IcingaConsoleError 'Unable to execute Job Type {0} because the specified file "{1}" does not exist' -Objects $JobType, $FilePath;
}
};
'InstallJEA' {
$WinAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ([string]::Format('-Command &{{ Use-Icinga -Minimal; Install-IcingaJEAProfile; Restart-IcingaWindowsService; }}', "'", $TmpFile.FullName, $FilePath))
Register-ScheduledTask -TaskName $TaskName -Action $WinAction -RunLevel Highest -TaskPath $TaskPath | Out-Null;
Start-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;

Wait-IcingaWindowsScheduledTask;

# No output data required for this task
};
Default {
Write-IcingaConsoleError 'Unable to execute Job Type {0}. Undefined operation' -Objects $JobType;
};
};

if (Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$FALSE -ErrorAction SilentlyContinue | Out-Null;
}

if (Test-Path $TmpFile) {
Remove-Item -Path $TmpFile -Force;
}

return $TaskData;
}
33 changes: 33 additions & 0 deletions lib/core/wintasks/Invoke-IcingaWindowsServiceHandlerTask.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function Invoke-IcingaWindowsServiceHandlerTask()
{
param (
[string]$ScriptPath = '',
[string]$ServiceName = '',
[string]$TmpFile = '',
[string]$TaskName = '',
[string]$TaskPath = ''
);

if ([string]::IsNullOrEmpty($ScriptPath)) {
return $null;
}

$ScriptPath = Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath $ScriptPath;

if ((Test-Path $ScriptPath) -eq $FALSE) {
Write-IcingaConsoleError 'Unable to execute Job. The provided script path "{0}" does not exist' -Objects $ScriptPath;
return $null;
}

$WinAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ([string]::Format("-WindowStyle Hidden -Command &{{ & '{0}' -ServiceName '{1}' -TmpFilePath '{2}' }}", $ScriptPath, $ServiceName, $TmpFile));
Register-ScheduledTask -TaskName $TaskName -Action $WinAction -RunLevel Highest -TaskPath $TaskPath | Out-Null;

Start-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;

Wait-IcingaWindowsScheduledTask;

[string]$TaskOutput = Read-IcingaFileSecure -File $TmpFile;
$TaskData = ConvertFrom-Json $TaskOutput;

return $TaskData;
}
26 changes: 26 additions & 0 deletions lib/core/wintasks/Wait-IcingaWindowsScheduledTask.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function Wait-IcingaWindowsScheduledTask()
{
param (
[string]$TaskName = 'Management Task',
[string]$TaskPath = '\Icinga\Icinga for Windows\',
[int]$Timeout = 180
);

[int]$TimeoutTicks = $Timeout * 1000;

while ($TimeoutTicks -gt 0) {
$TaskStatus = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;
if ($TaskStatus.State -eq 'Ready') {
break;
}
Start-Sleep -Milliseconds 500;

$TimeoutTicks -= 500;
}

if ($TimeoutTicks -le 0) {
Stop-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath | Out-Null;

Write-IcingaConsoleError 'The scheduled task "{0}" at path "{1}" could not be executed within {2} seconds and run into a timeout' -Objects $TaskName, $TaskPath, $Timeout;
}
}
26 changes: 26 additions & 0 deletions lib/core/wintasks/daemon/Register-TaskRenewCertificate.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function Register-IcingaWindowsScheduledTaskRenewCertificate()
{
param (
[switch]$Force = $FALSE
);

[string]$TaskName = 'Renew Certificate';
[string]$TaskPath = '\Icinga\Icinga for Windows\';

$RenewCertificateTask = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue;

if ($null -ne $RenewCertificateTask -And $Force -eq $FALSE) {
Write-IcingaConsoleWarning -Message 'The {0} task is already present. User -Force to enforce the re-creation' -Objects $TaskName;
return;
}

$ScriptPath = Join-Path -Path (Get-IcingaFrameworkRootPath) -ChildPath '\jobs\RenewCertificate.ps1';
$TaskTrigger = New-ScheduledTaskTrigger -Daily -DaysInterval 1 -At '1am';
$TaskAction = New-ScheduledTaskAction -Execute 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -Argument ([string]::Format("-WindowStyle Hidden -Command &{{ & '{0}' }}", $ScriptPath));
$TaskPrincipal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-544' -RunLevel 'Highest';
$TaskSettings = New-ScheduledTaskSettingsSet -DontStopIfGoingOnBatteries -AllowStartIfOnBatteries -StartWhenAvailable;

Register-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Force -Principal $TaskPrincipal -Action $TaskAction -Trigger $TaskTrigger -Settings $TaskSettings;

Write-IcingaConsoleWarning -Message 'The task "{0}" has been successfully registered at location "{1}".' -Objects $TaskName, $TaskPath;
}
14 changes: 14 additions & 0 deletions lib/core/wintasks/daemon/Start-TaskRenewCertificate.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function Start-IcingaWindowsScheduledTaskRenewCertificate()
{
[string]$TaskName = 'Renew Certificate';
[string]$TaskPath = '\Icinga\Icinga for Windows\';

$RenewCertificateTask = Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue;

if ($null -eq $RenewCertificateTask) {
Write-IcingaConsoleNotice -Message 'The "{0}" task is not present on this system.' -Objects $TaskName;
return;
}

Start-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath;
}
Loading

0 comments on commit 959fac6

Please sign in to comment.