Skip to content

Commit acbe232

Browse files
committed
Merge branch 'dev' into 'master'
Adds an option to ignore the source path when determining workload to be migrated; fixes the proactively locked host issue See merge request fozzy-winadmins/AutomaticMaintenance!25
2 parents 8bdd768 + 83182e4 commit acbe232

8 files changed

+53
-37
lines changed

AutomaticMaintenance-Hosts-Example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
"Workload": [
2121
{
2222
"Path": "D:\\VMs",
23+
"SourcePathFilterDisabled": true,
2324
"DestinationName": "SRV05",
2425
"DestinationPath": "E:\\VMs",
25-
"Filter": "$_.Name -notlike '*-DontMove'"
26+
"Filter": "$_.Name -notin @('StaticVM1', 'StaticVM2') -and $_.Location -notlike 'F:\\StaticVMs*'"
2627
}
2728
]
2829
},

AutomaticMaintenance.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@{
22
RootModule = 'AutomaticMaintenance.psm1'
3-
ModuleVersion = '2.10.0'
3+
ModuleVersion = '2.11.0'
44
GUID = '8e34abf8-40ba-4c68-8bf8-f235cd001d82'
55
Author = 'Kirill Nikolaev'
66
CompanyName = 'Fozzy Inc.'

AutomaticMaintenance.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ $ModuleName = ($MyInvocation.MyCommand.Name).Substring(0, ($MyInvocation.MyComma
2929
[System.TimeSpan]$ModuleWidePreventiveLockThreshold = New-Object -TypeName 'System.TimeSpan' -ArgumentList @(1, 0, 0)
3030
[bool]$ModuleWideSkipPreventivelyLocked = $true
3131
[bool]$ModuleWideSkipNotLockable = $true
32-
[string]$ModuleWideSkipPreventivelyLockedFullyQualifiedErrorId = 'SkipPreventivelyLockedHost'
32+
[string]$ModuleWideSkipPreventivelyLockedExceptionTarget = 'SkipPreventivelyLockedHost'
3333

3434
[int]$ModuleWideInstallUpdateTimeout = 60
3535
[System.TimeSpan]$ModuleWideInstallUpdateThreshold = New-Object -TypeName 'System.TimeSpan' -ArgumentList @(1, 0, 0)

Private/HV/Get-HVFilterString.ps1

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ function Get-HVFilterString {
66
[string]$Path,
77
[string]$Filter,
88
[ValidateSet('HV-SCVMM', 'HV-Vanilla')]
9-
[string]$Mode = 'HV-SCVMM'
9+
[string]$Mode = 'HV-SCVMM',
10+
[switch]$ExcludePathFromFilter
1011
)
1112

1213
$ErrorActionPreference = 'Stop'
@@ -19,25 +20,27 @@ function Get-HVFilterString {
1920
Write-Debug -Message ('$Path = ''{0}''' -f $Path)
2021
Write-Debug -Message ('$Filter = ''{0}''' -f $Filter)
2122
Write-Debug -Message ('$Mode = ''{0}''' -f $Mode)
23+
Write-Debug -Message ('$ExcludePathFromFilter = ${0}' -f $ExcludePathFromFilter)
2224

23-
Write-Debug -Message '$VMLocationPropertyName = switch ($Mode)'
24-
$VMLocationPropertyName = switch ($Mode) {
25-
'HV-SCVMM' {
26-
Write-Debug -Message 'Location'
27-
'Location'
25+
Write-Debug -Message 'if ($Path -and -not $ExcludePathFromFilter)'
26+
if ($Path -and -not $ExcludePathFromFilter) {
27+
Write-Debug -Message '$VMLocationPropertyName = switch ($Mode)'
28+
$VMLocationPropertyName = switch ($Mode) {
29+
'HV-SCVMM' {
30+
Write-Debug -Message 'Location'
31+
'Location'
32+
}
33+
'HV-Vanilla' {
34+
Write-Debug -Message 'Path'
35+
'Path'
36+
}
37+
Default {
38+
Write-Debug -Message 'Location'
39+
'Location'
40+
}
2841
}
29-
'HV-Vanilla' {
30-
Write-Debug -Message 'Path'
31-
'Path'
32-
}
33-
Default {
34-
Write-Debug -Message 'Location'
35-
'Location'
36-
}
37-
}
38-
Write-Debug -Message ('$VMLocationPropertyName = ''{0}''' -f $VMLocationPropertyName)
42+
Write-Debug -Message ('$VMLocationPropertyName = ''{0}''' -f $VMLocationPropertyName)
3943

40-
if ($Path) {
4144
Write-Debug -Message ('$FilterPath = [System.IO.Path]::Combine(''{0}'', ''*'')' -f $Path)
4245
$FilterPath = [System.IO.Path]::Combine($Path, '*') # Join-Path cannot combine paths on a drive which does not exist on the machine
4346
Write-Debug -Message ('$FilterPath = ''{0}''' -f $FilterPath)
@@ -57,8 +60,15 @@ function Get-HVFilterString {
5760
}
5861
Write-Debug -Message ('$FilterString = ''{0}''' -f $FilterString)
5962

60-
Write-Debug -Message ('[scriptblock]::Create(''{0}'')' -f $FilterString)
61-
[scriptblock]::Create($FilterString)
63+
Write-Debug -Message 'if ($FilterString)'
64+
if ($FilterString) {
65+
Write-Debug -Message ('[scriptblock]::Create(''{0}'')' -f $FilterString)
66+
[scriptblock]::Create($FilterString)
67+
}
68+
else {
69+
Write-Debug -Message '$null'
70+
$null
71+
}
6272

6373
Write-Debug -Message ('EXIT TRY {0}' -f $MyInvocation.MyCommand.Name)
6474
}

Private/HV/Get-HVFilterStringCompatible.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ function Get-HVFilterStringCompatible {
4141
}
4242
Write-Debug -Message ('$Filter = ''{0}''' -f $Filter)
4343

44-
Write-Debug -Message ('Get-HVFilterString -Path ''{0}'' -Filter ''{1}'' -Mode ''{2}''' -f $Path, $Filter, $Mode)
45-
Get-HVFilterString -Path $Path -Filter $Filter -Mode $Mode
44+
Write-Debug -Message ('Get-HVFilterString -Path ''{0}'' -Filter ''{1}'' -Mode ''{2}'' -ExcludePathFromFilter:${3}' -f $Path, $Filter, $Mode, [bool]$WorkloadPair.SourcePathFilterDisabled)
45+
Get-HVFilterString -Path $Path -Filter $Filter -Mode $Mode -ExcludePathFromFilter:$WorkloadPair.SourcePathFilterDisabled
4646

4747
Write-Debug -Message ('EXIT TRY {0}' -f $MyInvocation.MyCommand.Name)
4848
}

Private/Initialize-ComputerMaintenance.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function Initialize-ComputerMaintenance {
99
[ref]$DestinationHostLock,
1010
[switch]$SkipNotLockable,
1111
[switch]$SkipPreventivelyLocked,
12-
[string]$SkipPreventivelyLockedFullyQualifiedErrorId
12+
[string]$SkipPreventivelyLockedExceptionTarget
1313
)
1414

1515
$ErrorActionPreference = 'Stop'
@@ -26,6 +26,7 @@ function Initialize-ComputerMaintenance {
2626
Write-Debug -Message ('$DestinationHostLock.Value: ''{0}''' -f $DestinationHostLock.Value)
2727
Write-Debug -Message ('$SkipNotLockable = ${0}' -f $SkipNotLockable)
2828
Write-Debug -Message ('$SkipPreventivelyLocked = ${0}' -f $SkipPreventivelyLocked)
29+
Write-Debug -Message ('SkipPreventivelyLockedExceptionTarget = ''{0}''' -f $SkipPreventivelyLockedExceptionTarget)
2930

3031
Write-Debug -Message '$CallerName = Get-LockCallerName'
3132
$CallerName = Get-LockCallerName
@@ -77,7 +78,7 @@ function Initialize-ComputerMaintenance {
7778
Write-Debug -Message 'if ($SkipPreventivelyLocked)'
7879
if ($SkipPreventivelyLocked) {
7980
$Message = ('Skipping the computer {0} because it is locked by other sources for more than {1} already and $SkipPreventivelyLocked is {2}.' -f $ComputerName, [string]$PreventiveLockThreshold, [string]$SkipPreventivelyLocked)
80-
$PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.OperationCanceledException' -ArgumentList $Message), $SkipPreventivelyLockedFullyQualifiedErrorId, [System.Management.Automation.ErrorCategory]::OperationStopped, $null)))
81+
$PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.OperationCanceledException' -ArgumentList $Message), 'OperationStopped', [System.Management.Automation.ErrorCategory]::OperationStopped, $SkipPreventivelyLockedExceptionTarget)))
8182
}
8283
else {
8384
$Message = ('Computer {0} is locked by other sources for more than {1} already' -f $ComputerName, [string]$PreventiveLockThreshold)

Public/Invoke-ComputerMaintenance.ps1

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function Invoke-ComputerMaintenance {
66
[string]$ComputerName,
77
[int]$PreventiveLockTimeout = $ModuleWidePreventiveLockTimeout,
88
[System.TimeSpan]$PreventiveLockThreshold = $ModuleWidePreventiveLockThreshold,
9-
[string]$SkipPreventivelyLockedFullyQualifiedErrorId = $ModuleWideSkipPreventivelyLockedFullyQualifiedErrorId,
9+
[string]$SkipPreventivelyLockedExceptionTarget = $ModuleWideSkipPreventivelyLockedExceptionTarget,
1010
[switch]$SkipNotLockable = $ModuleWideSkipNotLockable,
1111
[switch]$SkipPreventivelyLocked = $ModuleWideSkipPreventivelyLocked,
1212
[switch]$EnableMaintenanceLog = $ModuleWideEnableMaintenanceLog
@@ -22,6 +22,7 @@ function Invoke-ComputerMaintenance {
2222
Write-Debug -Message ('$ComputerName = ''{0}''' -f $ComputerName)
2323
Write-Debug -Message ('$PreventiveLockTimeout = {0}' -f $PreventiveLockTimeout)
2424
Write-Debug -Message ('$PreventiveLockThreshold: ''{0}''' -f [string]$PreventiveLockThreshold)
25+
Write-Debug -Message ('SkipPreventivelyLockedExceptionTarget = ''{0}''' -f $SkipPreventivelyLockedExceptionTarget)
2526
Write-Debug -Message ('$SkipNotLockable = ${0}' -f $SkipNotLockable)
2627
Write-Debug -Message ('$SkipPreventivelyLocked = ${0}' -f $SkipPreventivelyLocked)
2728
Write-Debug -Message ('$EnableMaintenanceLog = ${0}' -f $EnableMaintenanceLog)
@@ -45,8 +46,8 @@ function Invoke-ComputerMaintenance {
4546
Write-Debug -Message 'if ($PendingReboot.ComponentBasedServicing)'
4647
if ($PendingReboot.ComponentBasedServicing) {
4748
# Sometimes, when CBS requires reboot, search for updates hangs up. Therefore, we need to reboot the machine first.
48-
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedFullyQualifiedErrorId ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedFullyQualifiedErrorId)
49-
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedFullyQualifiedErrorId $SkipPreventivelyLockedFullyQualifiedErrorId
49+
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedExceptionTarget ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedExceptionTarget)
50+
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedExceptionTarget $SkipPreventivelyLockedExceptionTarget
5051
Write-Debug -Message ('$ComputerWorkload: ''{0}''' -f [string]$ComputerWorkload)
5152

5253
Write-Debug -Message ('$HostLock: ''{0}''' -f $HostLock)
@@ -73,8 +74,8 @@ function Invoke-ComputerMaintenance {
7374
$IsMaintenanceNeeded = Test-MaintenanceNeeded -ComputerName $ComputerName
7475
Write-Debug -Message ('$IsMaintenanceNeeded = ${0}' -f $IsMaintenanceNeeded)
7576
if ($IsMaintenanceNeeded) {
76-
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedFullyQualifiedErrorId ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedFullyQualifiedErrorId)
77-
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedFullyQualifiedErrorId $SkipPreventivelyLockedFullyQualifiedErrorId
77+
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedExceptionTarget ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedExceptionTarget)
78+
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedExceptionTarget $SkipPreventivelyLockedExceptionTarget
7879
Write-Debug -Message ('$ComputerWorkload: ''{0}''' -f [string]$ComputerWorkload)
7980
Write-Debug -Message ('$HostLock: ''{0}''' -f $HostLock)
8081
Write-Debug -Message 'if ($HostLock)'
@@ -91,8 +92,8 @@ function Invoke-ComputerMaintenance {
9192
# If the machine does not need any updates, but just a reboot, let's reboot it.
9293
Write-Debug -Message ('$PendingReboot.IsRebootPending: ''{0}''' -f $PendingReboot.IsRebootPending)
9394
if ($PendingReboot.IsRebootPending) {
94-
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedFullyQualifiedErrorId ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedFullyQualifiedErrorId)
95-
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedFullyQualifiedErrorId $SkipPreventivelyLockedFullyQualifiedErrorId
95+
Write-Debug -Message ('$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName ''{0}'' -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:${1} -SkipPreventivelyLocked:${2} -SkipPreventivelyLockedExceptionTarget ''{3}''' -f $ComputerName, $SkipNotLockable, $SkipPreventivelyLocked, $SkipPreventivelyLockedExceptionTarget)
96+
$ComputerWorkload = Initialize-ComputerMaintenance -ComputerName $ComputerName -HostLock ([ref]$HostLock) -DestinationHostLock ([ref]$DestinationHostLock) -SkipNotLockable:$SkipNotLockable -SkipPreventivelyLocked:$SkipPreventivelyLocked -SkipPreventivelyLockedExceptionTarget $SkipPreventivelyLockedExceptionTarget
9697
Write-Debug -Message ('$ComputerWorkload: ''{0}''' -f [string]$ComputerWorkload)
9798
Write-Debug -Message ('$HostLock: ''{0}''' -f $HostLock)
9899
Write-Debug -Message 'if ($HostLock)'
@@ -114,9 +115,9 @@ function Invoke-ComputerMaintenance {
114115
catch {
115116
Write-Debug -Message ('ENTER CATCH {0}' -f $MyInvocation.MyCommand.Name)
116117

117-
Write-Debug -Message ('$_.FullyQualifiedErrorId: ''{0}''' -f $_.FullyQualifiedErrorId)
118-
Write-Debug -Message ('if ($_.FullyQualifiedErrorId -ne ''{0}'')' -f $SkipPreventivelyLockedFullyQualifiedErrorId)
119-
if ($_.FullyQualifiedErrorId -ne $SkipPreventivelyLockedFullyQualifiedErrorId) {
118+
Write-Debug -Message ('$_.TargetObject: ''{0}''' -f $_.TargetObject)
119+
Write-Debug -Message ('if ($_.TargetObject -ne ''{0}'')' -f $SkipPreventivelyLockedExceptionTarget)
120+
if ($_.TargetObject -ne $SkipPreventivelyLockedExceptionTarget) {
120121

121122
Write-Debug -Message ('{0}: $PSCmdlet.ThrowTerminatingError($_)' -f $MyInvocation.MyCommand.Name)
122123
$PSCmdlet.ThrowTerminatingError($_)

docs-additional/Configuration.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ Each attribute is usually a name of a PowerShell script, located in the `$Module
5555

5656
Workload objects have the following attributes:
5757

58-
* Path - Path where VM's configuration is located.
58+
* Path - Path where VM's configuration is located and where VM's configuration will be stored after maintenance.
5959
* DestinationName - Destination host's name where to move virtual machines.
6060
* DestinationPath - Local path on the destination host.
6161
* Filter - Defines a filter which will be used to pick VMs from the source host. For example, if you wish for some VMs not to mirate, but stay at the source host during maintenance, you can filter them out here.
62+
* DestinationFilter - This setting specifies the filter that will be applied to VMs on the migration hypervisor before the migration starts and allows to ignore a workload that is already on the hypervisor. Otherwise, the module's default behavior will stop processing, assuming that the migration hypervisor is already busy. It could be useful when the "Workload" block contains several sets of settings, or the migration hypervisor already has the virtual machines that are not related to ongoing maintenance.
63+
* SourcePathFilterDisabled - When set to `false` or not specified, the module applies the `Filter` option to the VMs located in the `Path` folder, therefore the module migrates filtered VMs from the `Path` folder only. When set to `true`, the module applies the `Filter` option to all VMs on the source host and migrates all filtered VMs. If both `SourcePathFilterDisabled` is `true` and `Filter` is not specified, all VMs will be considered as a workload to be migrated.
64+
6265

6366
#### HV-Vanilla
6467

0 commit comments

Comments
 (0)