Skip to content

Commit c9a285f

Browse files
Merge branch 'dev' into 'master'
Release v.2.6 See merge request fozzy-winadmins/AutomaticMaintenance!2
2 parents cac34c3 + df4ef9e commit c9a285f

5 files changed

Lines changed: 95 additions & 42 deletions

File tree

AutomaticMaintenance.psd1

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
@{
22
RootModule = 'AutomaticMaintenance.psm1'
3-
ModuleVersion = '2.5.1'
3+
ModuleVersion = '2.6'
44
GUID = '8e34abf8-40ba-4c68-8bf8-f235cd001d82'
55
Author = 'Kirill Nikolaev'
66
CompanyName = 'Fozzy Inc.'
77
Copyright = '(c) 2018 Fozzy Inc. All rights reserved.'
88
PowerShellVersion = '3.0'
9+
Description = 'Helps IT engineers to establish a continuous update process in large intertangled infrastructures.'
910
RequiredModules = @(
1011
'PendingReboot'
1112
'ResourceLocker'
@@ -19,4 +20,12 @@
1920
)
2021
CmdletsToExport = @()
2122
AliasesToExport = @()
23+
PrivateData = @{
24+
PSData = @{
25+
Tags = @()
26+
LicenseUri = 'https://github.com/FozzyHosting/AutomaticMaintenance/blob/master/LICENSE'
27+
ProjectUri = 'https://github.com/FozzyHosting/AutomaticMaintenance/'
28+
ReleaseNotes = ''
29+
}
30+
}
2231
}

Public/Invoke-InfrastructureMaintenance.ps1

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function Invoke-InfrastructureMaintenance {
3434
Write-Debug -Message 'if ($LogErrorFileContent)'
3535
if ($LogErrorFileContent) {
3636
$Message = ('Error log file {0} is not empty. To ignore this, set the module configuration variable $ModuleWideFailOnPreviousFailure to $false.' -f $LogErrorFilePath)
37-
PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.ApplicationException' -ArgumentList $Message), 'FileIsNotEmpty', [System.Management.Automation.ErrorCategory]::InvalidData, $null)))
37+
$PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.ApplicationException' -ArgumentList $Message), 'FileIsNotEmpty', [System.Management.Automation.ErrorCategory]::InvalidData, $null)))
3838
}
3939
}
4040
}
@@ -52,40 +52,70 @@ function Invoke-InfrastructureMaintenance {
5252
}
5353
}
5454

55-
Write-Debug -Message '$List = Get-ComputerList'
56-
$List = Get-ComputerList
57-
Write-Debug -Message ('$List: ''{0}''' -f [string]$List)
58-
59-
Write-Debug -Message 'if ($List)'
60-
if ($List) {
61-
foreach ($ComputerName in $List) {
62-
Write-Debug -Message ('$ComputerName = ''{0}''' -f $ComputerName)
63-
Write-Debug -Message ('$LogFilePath = ''{0}'' -f ''{1}''' -f $LogFilePathTemplate, $ComputerName)
64-
$LogFilePath = $LogFilePathTemplate -f $ComputerName
65-
Write-Debug -Message ('$LogFilePath = ''{0}''' -f $LogFilePath)
66-
67-
Write-Debug -Message ('$LogScriptBlock = [scriptblock]::Create((''Write-SimpleTextLog -Path ''''{{0}}'''' -MutexName ''''{{1}}'''''' -f {0}, {1}))' -f $LogFilePath, $LogMutexName)
68-
$LogScriptBlock = [scriptblock]::Create(('Write-SimpleTextLog -Path ''{0}'' -MutexName ''{1}''' -f $LogFilePath, $LogMutexName))
69-
Write-Debug -Message ('$LogScriptBlock = {0}' -f [string]$LogScriptBlock)
70-
71-
Write-Debug -Message ('$DebugLog: ''{0}''' -f [string]$DebugLog)
72-
Write-Debug -Message 'if ($DebugLog)'
73-
if ($DebugLog) {
74-
Write-Debug -Message ('$CurrentDebugPreference = ''{0}''' -f $global:DebugPreference)
75-
$CurrentDebugPreference = $global:DebugPreference
76-
Write-Debug -Message '$global:DebugPreference = ''Continue'''
77-
$global:DebugPreference = 'Continue'
78-
Write-Debug -Message ('Invoke-ComputerMaintenance -ComputerName ''{0}'' 5>&1 | Split-Output -ScriptBlock {{{1}}} -Mode Debug' -f $ComputerName, $LogScriptBlock)
79-
Invoke-ComputerMaintenance -ComputerName $ComputerName 5>&1 | Split-Output -ScriptBlock $LogScriptBlock -Mode Debug
55+
Write-Debug -Message '[System.Collections.ArrayList]$ProcessedComputers = @()'
56+
[System.Collections.ArrayList]$ProcessedComputers = @()
57+
58+
Write-Debug -Message 'do while ($UnprocessedComputers)'
59+
do {
60+
Write-Debug -Message '[System.Collections.ArrayList]$UnprocessedComputers = @()'
61+
[System.Collections.ArrayList]$UnprocessedComputers = @()
62+
63+
Write-Debug -Message '$List = Get-ComputerList'
64+
$List = Get-ComputerList
65+
Write-Debug -Message ('$List: ''{0}''' -f [string]$List)
66+
67+
Write-Debug -Message 'if ($List)'
68+
if ($List) {
69+
Write-Debug -Message 'foreach ($ComputerName in $List)'
70+
foreach ($ComputerName in $List) {
71+
Write-Debug -Message ('$ProcessedComputers: ''{0}''' -f [string]$ProcessedComputers)
72+
Write-Debug -Message ('$ComputerName = ''{0}''' -f $ComputerName)
73+
Write-Debug -Message ('if ($ProcessedComputers -notcontains $ComputerName)')
74+
if ($ProcessedComputers -notcontains $ComputerName) {
75+
Write-Debug -Message ('$null = $UnprocessedComputers.Add(''{0}'')' -f $ComputerName)
76+
$null = $UnprocessedComputers.Add($ComputerName)
77+
Write-Debug -Message ('$UnprocessedComputers: ''{0}''' -f [string]$UnprocessedComputers)
78+
}
8079
}
81-
else {
82-
Write-Debug -Message '$CurrentDebugPreference = $null'
83-
$CurrentDebugPreference = $null
84-
Write-Debug -Message ('Invoke-ComputerMaintenance -ComputerName ''{0}''' -f $ComputerName)
85-
Invoke-ComputerMaintenance -ComputerName $ComputerName
80+
81+
Write-Debug -Message ('$UnprocessedComputers: ''{0}''' -f [string]$UnprocessedComputers)
82+
Write-Debug -Message 'if ($UnprocessedComputers)'
83+
if ($UnprocessedComputers) {
84+
Write-Debug -Message '$ComputerName = $UnprocessedComputers[0]'
85+
$ComputerName = $UnprocessedComputers[0]
86+
Write-Debug -Message ('$ComputerName = ''{0}''' -f $ComputerName)
87+
88+
Write-Debug -Message ('$LogFilePath = ''{0}'' -f ''{1}''' -f $LogFilePathTemplate, $ComputerName)
89+
$LogFilePath = $LogFilePathTemplate -f $ComputerName
90+
Write-Debug -Message ('$LogFilePath = ''{0}''' -f $LogFilePath)
91+
92+
Write-Debug -Message ('$LogScriptBlock = [scriptblock]::Create((''Write-SimpleTextLog -Path ''''{{0}}'''' -MutexName ''''{{1}}'''''' -f {0}, {1}))' -f $LogFilePath, $LogMutexName)
93+
$LogScriptBlock = [scriptblock]::Create(('Write-SimpleTextLog -Path ''{0}'' -MutexName ''{1}''' -f $LogFilePath, $LogMutexName))
94+
Write-Debug -Message ('$LogScriptBlock = {0}' -f [string]$LogScriptBlock)
95+
96+
Write-Debug -Message ('$DebugLog: ''{0}''' -f [string]$DebugLog)
97+
Write-Debug -Message 'if ($DebugLog)'
98+
if ($DebugLog) {
99+
Write-Debug -Message ('$CurrentDebugPreference = ''{0}''' -f $global:DebugPreference)
100+
$CurrentDebugPreference = $global:DebugPreference
101+
Write-Debug -Message '$global:DebugPreference = ''Continue'''
102+
$global:DebugPreference = 'Continue'
103+
Write-Debug -Message ('Invoke-ComputerMaintenance -ComputerName ''{0}'' 5>&1 | Split-Output -ScriptBlock {{{1}}} -Mode Debug' -f $ComputerName, $LogScriptBlock)
104+
Invoke-ComputerMaintenance -ComputerName $ComputerName 5>&1 | Split-Output -ScriptBlock $LogScriptBlock -Mode Debug
105+
}
106+
else {
107+
Write-Debug -Message '$CurrentDebugPreference = $null'
108+
$CurrentDebugPreference = $null
109+
Write-Debug -Message ('Invoke-ComputerMaintenance -ComputerName ''{0}''' -f $ComputerName)
110+
Invoke-ComputerMaintenance -ComputerName $ComputerName
111+
}
112+
113+
Write-Debug -Message ('$null = $ProcessedComputers.Add(''{0}'')' -f $ComputerName)
114+
$null = $ProcessedComputers.Add($ComputerName)
115+
Write-Debug -Message ('$ProcessedComputers: ''{0}''' -f [string]$ProcessedComputers)
86116
}
87117
}
88-
}
118+
} while ($UnprocessedComputers)
89119

90120
Write-Debug -Message ('EXIT TRY {0}' -f $MyInvocation.MyCommand.Name)
91121
}
@@ -110,5 +140,5 @@ function Invoke-InfrastructureMaintenance {
110140
Write-Debug -Message ('EXIT FINALLY {0}' -f $MyInvocation.MyCommand.Name)
111141
}
112142

113-
Write-Debug -Message ('EXIT {0}' -f $MyInvocation.MyCommand.Name)
143+
Write-Debug -Message ('EXIT {0}' -f $MyInvocation.MyCommand.Name)
114144
}

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ The main function you need from this module is [**Invoke-InfrastructureMaintenan
1515

1616
Under the hood, **Invoke-InfrastructureMaintenance** uses another function to actually perform maintenance on each host — [**Invoke-ComputerMaintenance**](docs/Invoke-ComputerMaintenance.md). If you want to use an external orchestration/configuration management system (Ansible, Puppet etc.), configure it to execute **Invoke-ComputerMaintenance**, not **Invoke-InfrastructureMaintenance**.
1717

18+
## Update detection and installation
19+
**Invoke-ComputerMaintenance** uses the standard Windows Update API to detect and install updates. That means that to make updates available for a host, you have to make them available at WSUS or use direct connection to Microsoft Update. But you can exclude some updates from installation/detection:
20+
* Use the `UpdateInstallFilter` configuration attribute (or the `$ModuleWideInstallUpdateDefaultFilterString` module configuration variable) to specify filter for updates installation.
21+
* Use the `UpdateCheckFilter` configuration attribute (or the `$ModuleWideCheckUpdateDefaultFilterString` module configuration variable) to specify filter for updates detection.
22+
23+
If the detection process (**Test-WindowsUpdateNeeded**) finds available updates, the host maintenance process will start. Otherwise, **Invoke-ComputerMaintenance** skips to the next host.
24+
25+
There's no built-in way to install a specific update, but it is possible by leveraging step commands (see below), since you can execute custom commands and scripts there.
26+
1827
## Host types
1928
While the module can potentially support machines with various type of workloads, currently there are only two supported types:
2029
* **HV-SCVMM** - for stand-alone hypervisors (yes, *not* fail-over clusters) managed by SCVMM. Workload movement is provided by the [SCVMReliableMigration](https://github.com/FozzyHosting/SCVMReliableMigration) module.
@@ -85,8 +94,8 @@ There are several variables defined in the .psm1-file, which are used by the mod
8594
* `[System.TimeSpan]$ModuleWideInstallUpdateThreshold` - Specifies how long the module will wait for the update installation to finish.
8695
* `[string]$ModuleWideInstallUpdateTaskName` - The name of a Task Scheduler task which executes code to find and install updates.
8796
* `[string]$ModuleWideInstallUpdateTaskDescription` - The description of a Task Scheduler task which executes code to find and install updates.
88-
* `[string]$ModuleWideCheckUpdateDefaultFilterString` - A filter which is used to detect new updates.
89-
* `[string]$ModuleWideInstallUpdateDefaultFilterString` - A filter which is used during updates installation.
97+
* `[string]$ModuleWideCheckUpdateDefaultFilterString` - A filter which is used to detect new updates. Used if an `UpdateCheckFilter` attribute is not defined in host's configuration.
98+
* `[string]$ModuleWideInstallUpdateDefaultFilterString` - A filter which is used during updates installation. Used if an `UpdateInstallFilter` attribute is not defined in host's configuration.
9099
* `[string]$ModuleWideUpdateSearchCriteria` - Criteria for the IUpdateSearcher::Search method (https://docs.microsoft.com/en-us/windows/desktop/api/wuapi/nf-wuapi-iupdatesearcher-search)
91100

92101
## Loading variables from an external source

docs-additional/Configuration.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ The `Include` attribute defines a collection of objects. Each of those objects m
2424

2525
## Attributes
2626
* Name - The name of a host where you want to install updates automatically. Should be unique.
27-
* Type - A type of a host. Currently, acceptable values are "HV-SCVMM", "Generic".
27+
* Type - A type of a host. Currently, acceptable values are `HV-SCVMM`, `Generic`.
2828
* UpdateInstallFilter - A filter which is used to filter out unneeded updates, like preview versions etc.
29+
* Disabled - Settings this attribute to `True` allows you to temporary disable processing of this particular host. Useful when you need to perform some manual maintenance.
2930

3031
The following set of attributes describes step commands (plug-ins):
3132
* PreClearCommands
@@ -35,7 +36,7 @@ The following set of attributes describes step commands (plug-ins):
3536
* TestCommands
3637
* FinallyCommands
3738

38-
Each attribute is usually a name of a PowerShell script, located in a "ScriptBlocks" folder in the module's folder. See more about these commands [here](Step-Commands.md)
39+
Each attribute is usually a name of a PowerShell script, located in a `ScriptBlocks` folder in the module's folder. See more about these commands [here](Step-Commands.md)
3940

4041
### Workload-specific attributes
4142
#### HV-SCVMM

docs-additional/Step-Commands.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
# Plug-ins (Step Commands)
2-
There are several steps defined (in the order of execute):
1+
# Step Commands (Plug-ins)
2+
Step commands are scripts which can be run at steps executed throughout the maintenance process, as defined below. Usually you would like to create a PowerShell script, put it into the `ScriptBlocks` folder, then insert the name of the script into an appropriate attribute in the main configuration file or in the templates file (or in both, if your config requires so - it's completely up to you).
3+
4+
Step commands are good in letting other systems know that a host is about to reboot or it has returned back into service, but you of course can find them useful in other ways as well.
5+
6+
There are several steps defined (in the order of execution):
37
* Pre-Clear (PreClearCommands) - this step executes before workload is removed from the host. It's a good step to execute a command which will prevent further workload placing on the host.
48
* Post-Clear (PostClearCommands) - this step executes right after workload is removed from the host. At this step you can disable monitoring, for example.
59
* Test (TestCommands) - this step executes after the host is back after reboot. Its purpose is to run commands which ensure that it is safe to move workload back to the host.
610
* Pre-Restore (PreRestoreCommands) - executes before moving workload back to the host. We recommend to enable monitoring here,if you disabled it at "Post-Clear".
711
* Post-Restore (PostRestoreCommands) - executes when workload moving back has completed. If you prevented workload placement earlier, you can enable it now.
8-
* Finally (FinallyCommands) - this step always executes at the end of the process. If an error happens, it will execute after error processing.
12+
* Finally (FinallyCommands) - this step always executes at the end of the process. If an error happens, it will execute after error processing (The whole function is in a `try` block and `FinallyCommands` run at its `finally` section).
913

1014
## Requirements
11-
Executable files (usually PowerShell scripts), specified in those attributes must accept two following parameters:
15+
Executable files (usually, PowerShell scripts), specified in those attributes must be in the `ScriptBlocks` folder and must accept two following parameters:
1216
* ComputerName - A string, containing a name of a computer which is in progress right now. Mandatory.
1317
* Variables - A collection of `System.Management.Automation.PSVariable` objects. Might be empty, but unlikely.
1418

0 commit comments

Comments
 (0)