From 81541a83f2fc49402b5e56c9534c8c8da04888ab Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 2 Nov 2024 16:29:15 +0100 Subject: [PATCH 01/26] Add whatif support --- resources/PythonPip3Dsc/PythonPip3Dsc.psd1 | 163 +++++++++++---------- resources/PythonPip3Dsc/PythonPip3Dsc.psm1 | 120 ++++++++++++++- 2 files changed, 196 insertions(+), 87 deletions(-) diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psd1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psd1 index cebf75ed..a95da903 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psd1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psd1 @@ -8,127 +8,128 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'PythonPip3Dsc.psm1' + # Script module or binary module file associated with this manifest. + RootModule = 'PythonPip3Dsc.psm1' -# Version number of this module. -ModuleVersion = '0.1.0' + # Version number of this module. + ModuleVersion = '0.1.0' -# Supported PSEditions -# CompatiblePSEditions = @() + # Supported PSEditions + # CompatiblePSEditions = @() -# ID used to uniquely identify this module -GUID = 'bc1cab01-7e6f-4bba-a6ec-d77d0ffe91c7' + # ID used to uniquely identify this module + GUID = 'bc1cab01-7e6f-4bba-a6ec-d77d0ffe91c7' -# Author of this module -Author = 'DscSamples' + # Author of this module + Author = 'DscSamples' -# Company or vendor of this module -# CompanyName = '' + # Company or vendor of this module + # CompanyName = '' -# Copyright statement for this module -# Copyright = '' + # Copyright statement for this module + # Copyright = '' -# Description of the functionality provided by this module -Description = 'DSC Resource for Python pip3' + # Description of the functionality provided by this module + Description = 'DSC Resource for Python pip3' -# Minimum version of the PowerShell engine required by this module -# PowerShellVersion = '' + # Minimum version of the PowerShell engine required by this module + # PowerShellVersion = '' -# Name of the PowerShell host required by this module -# PowerShellHostName = '' + # Name of the PowerShell host required by this module + # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -# NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -# FunctionsToExport = @() + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + # FunctionsToExport = @() -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -# CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + # CmdletsToExport = @() -# Variables to export from this module -# VariablesToExport = '*' + # Variables to export from this module + # VariablesToExport = '*' -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -# AliasesToExport = @() + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + # AliasesToExport = @() -# DSC resources to export from this module -DscResourcesToExport = @( - 'Pip3Package' -) + # DSC resources to export from this module + DscResourcesToExport = @( + 'Pip3Package' + ) -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('PSDscResource_Pip3Package') + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @('PSDscResource_Pip3Package') - # A URL to the license for this module. - LicenseUri = 'https://github.com/microsoft/winget-dsc/blob/main/LICENSE' + # A URL to the license for this module. + LicenseUri = 'https://github.com/microsoft/winget-dsc/blob/main/LICENSE' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/microsoft/winget-dsc' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/winget-dsc' - # A URL to an icon representing this module. - # IconUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # ReleaseNotes of this module - # ReleaseNotes = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # Prerelease string of this module - Prerelease = 'alpha' + # Prerelease string of this module + Prerelease = 'alpha' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() + DscCapabilities = @('Get', 'Set', 'Test', 'Export', 'WhatIf') - } # End of PSData hashtable + } # End of PSData hashtable -} # End of PrivateData hashtable + } # End of PrivateData hashtable -# HelpInfo URI of this module -# HelpInfoURI = '' + # HelpInfo URI of this module + # HelpInfoURI = '' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 index 1a138b36..4e884164 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 @@ -4,6 +4,77 @@ using namespace System.Collections.Generic #region Functions +function Invoke-Process +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList, + + [ValidateSet("Full", "StdOut", "StdErr", "ExitCode", "None")] + [string]$DisplayLevel + ) + + try + { + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $FilePath + $pinfo.RedirectStandardError = $true + $pinfo.RedirectStandardOutput = $true + $pinfo.UseShellExecute = $false + $pinfo.WindowStyle = 'Hidden' + $pinfo.CreateNoWindow = $true + $pinfo.Arguments = $ArgumentList + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + + $stOut = @() + while (-not $p.StandardOutput.EndOfStream) + { + $stOut += $p.StandardOutput.ReadLine() + } + + $stErr = @() + while (-not $p.StandardError.EndOfStream) + { + $stErr += $p.StandardError.ReadLine() + } + + $result = [pscustomobject]@{ + Title = ($MyInvocation.MyCommand).Name + Command = $FilePath + Arguments = $ArgumentList + StdOut = $stOut + StdErr = $stErr + ExitCode = $p.ExitCode + } + + $p.WaitForExit() + + if (-not([string]::IsNullOrEmpty($DisplayLevel))) + { + switch ($DisplayLevel) + { + "Full" { return $result; break } + "StdOut" { return $result.StdOut; break } + "StdErr" { return $result.StdErr; break } + "ExitCode" { return $result.ExitCode; break } + } + } + } + catch + { + exit 1 + } +} + function Get-Pip3Path { if ($IsWindows) @@ -106,7 +177,10 @@ function Get-PackageNameWithVersion [string]$Version, [Parameter()] - [switch]$IsUpdate + [switch]$IsUpdate, + + [Parameter()] + [switch]$DryRun ) if ($PSBoundParameters.ContainsKey('Version') -and -not ([string]::IsNullOrEmpty($Version))) @@ -130,7 +204,10 @@ function Invoke-Pip3Install [string]$Version, [Parameter()] - [switch]$IsUpdate + [switch]$IsUpdate, + + [Parameter()] + [switch]$DryRun ) $command = [List[string]]::new() @@ -140,8 +217,16 @@ function Invoke-Pip3Install { $command.Add("--force-reinstall") } + if ($DryRun.IsPresent) + { + $command.Add("--dry-run") + } + $command.Add($Arguments) - return Invoke-Pip3 -command $command + Write-Verbose -Message "Executing 'pip' install with command: $command" + $result = Invoke-Pip3 -command $command + + return $result } function Invoke-Pip3Uninstall @@ -164,6 +249,7 @@ function Invoke-Pip3Uninstall # '--yes' is needed to ignore confrimation required for uninstalls $command.Add("--yes") + Write-Verbose -Message "Executing 'pip' uninstall with command: $command" return Invoke-Pip3 -command $command } @@ -249,11 +335,11 @@ function Invoke-Pip3 if ($global:usePip3Exe) { - return Start-Process -FilePath $global:pip3ExePath -ArgumentList $command -Wait -PassThru -WindowStyle Hidden + return Invoke-Process -FilePath $global:pip3ExePath -ArgumentList $command -DisplayLevel Full } else { - return Start-Process -FilePath pip3 -ArgumentList $command -Wait -PassThru -WindowStyle hidden + return Invoke-Process -FilePath pip3 -ArgumentList $command -DisplayLevel Full } } @@ -399,6 +485,28 @@ class Pip3Package } } + [string] WhatIf() + { + if ($this.Exist) + { + $whatIfState = Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments -DryRun + + $out = @{ + PackageName = $this.PackageName + _metaData = @{ + whatIf = $whatIfState.StdOut + } + } + } + else + { + # Uninstall does not have --dry-run param + $out = @{} + } + + return ($out | ConvertTo-Json -Depth 10 -Compress) + } + static [Pip3Package[]] Export() { $packages = GetInstalledPip3Packages @@ -436,4 +544,4 @@ class Pip3Package #endRegion Pip3Package Helper functions } -#endregion DSCResources +#endregion DSCResources \ No newline at end of file From 323d6246cb509505f092ea3433fad7bc33435dab Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 10:40:47 +0100 Subject: [PATCH 02/26] Initial module for Windows Update settings --- ...crosoft.Windows.Setting.WindowsUpdate.psd1 | 134 +++++++++ ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 284 ++++++++++++++++++ ...ft.Windows.Setting.WindowsUpdate.Tests.ps1 | 24 ++ 3 files changed, 442 insertions(+) create mode 100644 resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 create mode 100644 resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 create mode 100644 tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 new file mode 100644 index 00000000..7636d788 --- /dev/null +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 @@ -0,0 +1,134 @@ +# +# Module manifest for module 'Microsoft.Windows.Setting.WindowsUpdate' +# +# Generated by: Microsoft Corporation +# +# Generated on: 04/11/2024 +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'Microsoft.Windows.Setting.WindowsUpdate.psm1' + + # Version number of this module. + ModuleVersion = '0.1.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = '6a0a9e72-9797-4c28-94ca-ebfbef3d7116' + + # Author of this module + Author = 'Microsoft Corporation' + + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' + + # Copyright statement for this module + Copyright = '(c) Microsoft Corporation. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'DSC Resource for Windows Update Settings' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '7.2' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = '*' + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = '*' + + # Variables to export from this module + VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = '*' + + # DSC resources to export from this module + DscResourcesToExport = @('WindowsUpdate') + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @( + 'PSDscResource_WindowsUpdate' + ) + + # A URL to the license for this module. + LicenseUri = 'https://github.com/microsoft/winget-dsc/blob/main/LICENSE' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/winget-dsc' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = 'alpha' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} + diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 new file mode 100644 index 00000000..20eccd63 --- /dev/null +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -0,0 +1,284 @@ +$global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' + +#region Functions +function DoesRegistryKeyPropertyExist +{ + param ( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter(Mandatory)] + [string]$Name + ) + + # Get-ItemProperty will return $null if the registry key property does not exist. + $itemProperty = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue + return $null -ne $itemProperty +} + +function Test-WindowsUpdateRegistryKey +{ + param ( + [Parameter(Mandatory)] + [hashtable] $RegistryKeyProperty, + + [Parameter(Mandatory)] + [WindowsUpdate]$CurrentState + ) + + $result = $true + foreach ($key in $RegistryKeyProperty.Keys) + { + $value = $RegistryKeyProperty[$key] + if ($value -ne $CurrentState.$key) + { + $result = $false + } + } + + return $result +} + +function Set-WindowsUpdateRegistryKey +{ + param ( + [Parameter(Mandatory)] + [string]$Path, + + [Parameter()] + [AllowNull()] + [hashtable] $RegistryKeyProperty + ) + + if (-not (Test-Path -Path $Path)) + { + $null = New-Item -Path $Path -Force + } + + foreach ($key in $RegistryKeyProperty.Keys) + { + $value = $RegistryKeyProperty[$key] + $typeInfo = $value.GetType().Name + + if ($typeInfo -eq 'Boolean') + { + $value = [int]$value + } + + if ($typeInfo -eq 'Int32' -and $key -in @('UserChoiceActiveHoursEnd', 'UserChoiceActiveHoursStart')) + { + if ($value -notin (0..24)) + { + Throw "Value for $key must be between 0 and 24" + } + } + + if (-not (DoesRegistryKeyPropertyExist -Path $Path -Name $key)) + { + $null = New-ItemProperty -Path $Path -Name $key -Value $value -PropertyType 'DWord' -Force + } + + Write-Verbose -Message "Setting $key to $($RegistryKeyProperty[$key])" + Set-ItemProperty -Path $Path -Name $key -Value $value + } +} +#endregion Functions + +#region Classes +[DSCResource()] +class WindowsUpdate +{ + # Key required. Do not set. + [DscProperty(Key)] + [string] $SID + + [DscProperty()] + [nullable[bool]] $IsContinuousInnovationOptedIn + + [DscProperty()] + [nullable[bool]] $AllowMUUpdateService + + [DscProperty()] + [nullable[bool]] $IsExpedited + + [DscProperty()] + [nullable[bool]] $AllowAutoWindowsUpdateDownloadOverMeteredNetwork + + [DscProperty()] + [nullable[bool]] $RestartNotificationsAllowed + + [DscProperty()] + [nullable[bool]] $SmartActiveHoursState + + [DscProperty()] + [nullable[int]] $UserChoiceActiveHoursEnd + + [DscProperty()] + [nullable[int]] $UserChoiceActiveHoursStart + + static hidden [string] $IsContinuousInnovationOptedInProperty = 'IsContinuousInnovationOptedIn' + static hidden [string] $AllowMUUpdateServiceProperty = 'AllowMUUpdateService' + static hidden [string] $IsExpeditedProperty = 'IsExpedited' + static hidden [string] $AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty = 'AllowAutoWindowsUpdateDownloadOverMeteredNetwork' + static hidden [string] $RestartNotificationsAllowedProperty = 'RestartNotificationsAllowed2' + static hidden [string] $SmartActiveHoursStateProperty = 'SmartActiveHoursState' + static hidden [string] $UserChoiceActiveHoursEndProperty = 'UserChoiceActiveHoursEnd' + static hidden [string] $UserChoiceActiveHoursStartProperty = 'UserChoiceActiveHoursStart' + + [WindowsUpdate] Get() + { + $currentState = [WindowsUpdate]::new() + $currentState.IsContinuousInnovationOptedIn = [WindowsUpdate]::GetIsContinuousInnovationOptedInStatus() + $currentState.AllowMUUpdateService = [WindowsUpdate]::AllowMUUpdateServiceStatus() + $currentState.IsExpedited = [WindowsUpdate]::IsExpeditedStatus() + $currentState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork = [WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkStatus() + $currentState.RestartNotificationsAllowed = [WindowsUpdate]::RestartNotificationsAllowedStatus() + $currentState.SmartActiveHoursState = [WindowsUpdate]::SmartActiveHoursStateStatus() + $currentState.UserChoiceActiveHoursEnd = [WindowsUpdate]::UserChoiceActiveHoursEndStatus() + $currentState.UserChoiceActiveHoursStart = [WindowsUpdate]::UserChoiceActiveHoursStartStatus() + + return $currentState + } + + [bool] Test() + { + $currentState = $this.Get() + $settableProperties = $this.GetParameters() + return (Test-WindowsUpdateRegistryKey -RegistryKeyProperty $settableProperties -CurrentState $currentState) + } + + [void] Set() + { + if ($this.Test()) + { + return + } + + $parameters = $this.GetParameters() + + Set-WindowsUpdateRegistryKey -Path $global:WindowsUpdateSettingPath -RegistryKeyProperty $parameters + } + + #region WindowsUpdate helper functions + static [bool] GetIsContinuousInnovationOptedInStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsContinuousInnovationOptedInProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsContinuousInnovationOptedInProperty).IsContinuousInnovationOptedInProperty + return $value + } + } + + static [bool] AllowMUUpdateServiceStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowMUUpdateServiceProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowMUUpdateServiceProperty).AllowMUUpdateServiceProperty + return $value + } + } + + static [bool] IsExpeditedStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsExpeditedProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsExpeditedProperty).IsExpeditedProperty + return $value + } + } + + static [bool] AllowAutoWindowsUpdateDownloadOverMeteredNetworkStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty).AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty + return $value + } + } + + static [bool] RestartNotificationsAllowedStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::RestartNotificationsAllowedProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::RestartNotificationsAllowedProperty).RestartNotificationsAllowed + return $value + } + } + + static [bool] SmartActiveHoursStateStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::SmartActiveHoursStateProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::SmartActiveHoursStateProperty).SmartActiveHoursState + return $value + } + } + + static [int] UserChoiceActiveHoursEndStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursEndProperty))) + { + return $false + } + else + { + # there is some weird behaviour with integers in the registry, so we need to get the value from the property + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursEndProperty) | Select-Object -ExpandProperty UserChoiceActiveHoursEnd + + return $value + } + } + + static [int] UserChoiceActiveHoursStartStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursStartProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursStartProperty) | Select-Object -ExpandProperty UserChoiceActiveHoursStart + return $value + } + } + + [hashtable] GetParameters() + { + $parameters = @{} + foreach ($property in $this.PSObject.Properties) + { + if (-not ([string]::IsNullOrEmpty($property.Value))) + { + $parameters[$property.Name] = $property.Value + } + } + + return $parameters + } + #endRegion WindowsUpdate helper functions +} +#endRegion classes \ No newline at end of file diff --git a/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 b/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 new file mode 100644 index 00000000..229d2bce --- /dev/null +++ b/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +using module Microsoft.Windows.Setting.WindowsUpdate + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +<# +.Synopsis + Pester tests related to the Microsoft.Windows.Setting.WindowsUpdate PowerShell module. +#> + +BeforeAll { + Import-Module Microsoft.Windows.Setting.WindowsUpdate +} + +Describe 'List available DSC resources' { + It 'Shows DSC Resources' { + $expectedDSCResources = "WindowsUpdate" + $availableDSCResources = (Get-DscResource -Module Microsoft.Windows.Setting.WindowsUpdate).Name + $availableDSCResources.count | Should -Be 1 + $availableDSCResources | Where-Object { $expectedDSCResources -notcontains $_ } | Should -BeNullOrEmpty -ErrorAction Stop + } +} \ No newline at end of file From 0ba034a4846e39d6c6d48b5827c7e8bf5a509846 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 12:03:53 +0100 Subject: [PATCH 03/26] TODO task --- .../Microsoft.Windows.Setting.WindowsUpdate.psm1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index 20eccd63..a2d62a92 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -116,6 +116,8 @@ class WindowsUpdate [DscProperty()] [nullable[int]] $UserChoiceActiveHoursStart + # TODO: Add delivery options + static hidden [string] $IsContinuousInnovationOptedInProperty = 'IsContinuousInnovationOptedIn' static hidden [string] $AllowMUUpdateServiceProperty = 'AllowMUUpdateService' static hidden [string] $IsExpeditedProperty = 'IsExpedited' From 7ba6a698880b90d21f43834c6cb924b3e35a858f Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 12:58:38 +0100 Subject: [PATCH 04/26] Add assertions for delivery optimization --- ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 210 +++++++++++++++++- 1 file changed, 202 insertions(+), 8 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index a2d62a92..bd60c883 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -1,4 +1,5 @@ $global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' +$global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' # The network service account using wmiprvse.exe sets values in the user hive #region Functions function DoesRegistryKeyPropertyExist @@ -65,13 +66,14 @@ function Set-WindowsUpdateRegistryKey $value = [int]$value } - if ($typeInfo -eq 'Int32' -and $key -in @('UserChoiceActiveHoursEnd', 'UserChoiceActiveHoursStart')) - { - if ($value -notin (0..24)) - { - Throw "Value for $key must be between 0 and 24" - } - } + # validate the value of UserChoiceActiveHoursEnd and UserChoiceActiveHoursStart to be between 0 and 24 + Assert-UserChoiceValue -KeyName $key -Value $value + + # validate the value of DownloadRateBackgroundPct, DownloadRateForegroundPct and UpRatePctBandwith to be between 0 and 100 + Assert-RatePercentageValue -KeyName $key -Value $value + + # validate the value of UpRatePctBandwith to be between 5 and 500 + Assert-UpRateValue -KeyName $key -Value $value if (-not (DoesRegistryKeyPropertyExist -Path $Path -Name $key)) { @@ -81,6 +83,71 @@ function Set-WindowsUpdateRegistryKey Write-Verbose -Message "Setting $key to $($RegistryKeyProperty[$key])" Set-ItemProperty -Path $Path -Name $key -Value $value } +} + +function Assert-UpRateValue +{ + param ( + [Parameter(Mandatory)] + [string] $KeyName, + + [Parameter(Mandatory)] + [int] $Value + ) + + if ($KeyName -eq 'UpRatePctBandwidth' -and $Value -notin (5..500)) + { + Throw "You are specifying a percentage value, which must be between 5 and 500. The value you provided is $Value. Please provide a value between 5 and 500." + } +} + +function Assert-RatePercentageValue +{ + param ( + [Parameter(Mandatory)] + [string] $KeyName, + + [Parameter(Mandatory)] + [int] $Value + ) + + if ($KeyName -in ('DownloadRateBackgroundPct', 'DownloadRateForegroundPct', 'UpRatePctBandwidth') -and $Value -notin (0..100)) + { + # TODO: It might be beneficial to add `Reasons` and not throw, only return statement + Throw "You are specifying a percentage value, which must be between 0 and 100. The value you provided is $Value. Please provide a value between 0 and 100." + } +} + +function Assert-UserChoiceValue +{ + param ( + [Parameter(Mandatory)] + [string] $KeyName, + + [Parameter(Mandatory)] + [int] $Value + ) + + if ($KeyName -in ('UserChoiceActiveHoursEnd', 'UserChoiceActiveHoursStart') -and $Value -notin (0..24)) + { + Throw "Value must be between 0 and 24" + } +} + +function Assert-DownloadRate +{ + param ( + [Parameter(Mandatory)] + [hashtable] $Parameters + ) + + if ($Parameters.ContainsKey('DownloadRateBackgroundPct') -or $Parameters.ContainsKey('DownloadRateForegroundPct')) + { + if ($Parameters.ContainsKey('DownloadRateBackgroundBps') -or $Parameters.ContainsKey('DownloadRateForegroundBps')) + { + Throw "Cannot set both DownloadRateBackgroundPct/DownloadRateForegroundPct and DownloadRateBackgroundBps/DownloadRateForegroundBps" + } + } } #endregion Functions @@ -116,7 +183,27 @@ class WindowsUpdate [DscProperty()] [nullable[int]] $UserChoiceActiveHoursStart - # TODO: Add delivery options + [DscProperty()] + [ValidateSet(0, 1, 3)] + [nullable[int]] $DownloadMode + + [DscProperty()] + [nullable[int]] $DownloadRateBackgroundBps + + [DscProperty()] + [nullable[int]] $DownloadRateForegroundBps + + [DscProperty()] + [nullable[int]] $DownloadRateBackgroundPct + + [DscProperty()] + [nullable[int]] $DownloadRateForegroundPct + + [DscProperty()] + [nullable[int]] $UploadLimitGBMonth + + [DscProperty()] + [nullable[int]] $UpRatePctBandwidth static hidden [string] $IsContinuousInnovationOptedInProperty = 'IsContinuousInnovationOptedIn' static hidden [string] $AllowMUUpdateServiceProperty = 'AllowMUUpdateService' @@ -126,6 +213,13 @@ class WindowsUpdate static hidden [string] $SmartActiveHoursStateProperty = 'SmartActiveHoursState' static hidden [string] $UserChoiceActiveHoursEndProperty = 'UserChoiceActiveHoursEnd' static hidden [string] $UserChoiceActiveHoursStartProperty = 'UserChoiceActiveHoursStart' + static hidden [string] $DownloadModeProperty = 'DownloadMode' + static hidden [string] $DownloadRateBackgroundBpsProperty = 'DownloadRateBackgroundBps' + static hidden [string] $DownloadRateForegroundBpsProperty = 'DownloadRateForegroundBps' + static hidden [string] $DownloadRateBackgroundPctProperty = 'DownloadRateBackgroundPct' + static hidden [string] $DownloadRateForegroundPctProperty = 'DownloadRateForegroundPct' + static hidden [string] $UploadLimitGBMonthProperty = 'UploadLimitGBMonth' + static hidden [string] $UpRatePctBandwidthProperty = 'UpRatePctBandwidth' [WindowsUpdate] Get() { @@ -138,6 +232,13 @@ class WindowsUpdate $currentState.SmartActiveHoursState = [WindowsUpdate]::SmartActiveHoursStateStatus() $currentState.UserChoiceActiveHoursEnd = [WindowsUpdate]::UserChoiceActiveHoursEndStatus() $currentState.UserChoiceActiveHoursStart = [WindowsUpdate]::UserChoiceActiveHoursStartStatus() + $currentState.DownloadMode = [WindowsUpdate]::DownloadModeStatus() + $currentState.DownloadRateBackgroundBps = [WindowsUpdate]::DownloadRateBackGroundBps() + $currentState.DownloadRateForegroundBps = [WindowsUpdate]::DownloadRateForegroundBps() + $currentState.DownloadRateBackgroundPct = [WindowsUpdate]::DownloadRateBackgroundPctStatus() + $currentState.DownloadRateForegroundPct = [WindowsUpdate]::DownloadRateForegroundPctStatus() + $currentState.UploadLimitGBMonth = [WindowsUpdate]::UploadLimitGBMonthStatus() + $currentState.UpRatePctBandwidth = [WindowsUpdate]::UpRatePctBandwidthStatus() return $currentState } @@ -158,6 +259,8 @@ class WindowsUpdate $parameters = $this.GetParameters() + Assert-DownloadRate -Parameters $parameters + Set-WindowsUpdateRegistryKey -Path $global:WindowsUpdateSettingPath -RegistryKeyProperty $parameters } @@ -268,6 +371,97 @@ class WindowsUpdate } } + static [int] DownloadModeStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadModeProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadModeProperty) | Select-Object -ExpandProperty DownloadMode + return $value + } + } + + static [int] DownloadRateBackGroundBps() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackGroundBpsProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackGroundBpsProperty) | Select-Object -ExpandProperty DownloadRateBackGroundBps + return $value + } + } + + static [int] DownloadRateForegroundBps() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundBpsProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundBpsProperty) | Select-Object -ExpandProperty DownloadRateForegroundBps + return $value + } + } + + static [int] DownloadRateBackgroundPctStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackgroundPctProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackgroundPctProperty) | Select-Object -ExpandProperty DownloadRateBackgroundPct + return $value + } + } + + static [int] DownloadRateForegroundPctStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundPctProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundPctProperty) | Select-Object -ExpandProperty DownloadRateForegroundPct + return $value + } + } + + static [int] UploadLimitGBMonthStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UploadLimitGBMonthProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UploadLimitGBMonthProperty) | Select-Object -ExpandProperty UploadLimitGBMonth + return $value + } + } + + static [int] UpRatePctBandwidthStatus() + { + if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UpRatePctBandwidthProperty))) + { + return $false + } + else + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UpRatePctBandwidthProperty) | Select-Object -ExpandProperty UpRatePctBandwidth + return $value + } + } + [hashtable] GetParameters() { $parameters = @{} From 81b64c375857e4a87febb8dae8e2620920e4e5cf Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 13:10:04 +0100 Subject: [PATCH 05/26] Update the docs --- .../WindowsUpdate.md | 0 ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md diff --git a/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md b/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md new file mode 100644 index 00000000..e69de29b diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index bd60c883..a20b9e4f 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -152,6 +152,63 @@ function Assert-DownloadRate #endregion Functions #region Classes +<# +.SYNOPSIS + The `WindowsUpdate` DSC resource allows you to configure various Windows Update settings, including enabling or disabling specific update services, setting download and upload rates, and configuring active hours for updates. + +.PARAMETER SID + The security identifier. This is a key property and should not be set manually. + +.PARAMETER IsContinuousInnovationOptedIn + Indicates whether the device is opted in for continuous innovation updates. This is the setting in Windows Update settings -> Get the latest updates as soon as they're available. + +.PARAMETER AllowMUUpdateService + Indicates whether the Microsoft Update service is allowed. This is the setting in Windows Update settings -> Advanced options -> Receive updates for other Microsoft products. + +.PARAMETER IsExpedited + Indicates whether the updates are expedited. This is the setting in Windows Update settings -> Advanced options -> Get me up to date. + +.PARAMETER AllowAutoWindowsUpdateDownloadOverMeteredNetwork + Indicates whether automatic Windows Update downloads are allowed over metered networks. This is the setting in Windows Update settings -> Advanced options -> Download updates over metered connections. + +.PARAMETER RestartNotificationsAllowed + Indicates whether restart notifications are allowed. This is the setting in Windows Update settings -> Advanced options -> Notify me when a restart is required to finish updating. + +.PARAMETER SmartActiveHoursState + Indicates whether smart active hours are enabled. + +.PARAMETER UserChoiceActiveHoursEnd + The end time for user-chosen active hours. + +.PARAMETER UserChoiceActiveHoursStart + The start time for user-chosen active hours. + +.PARAMETER DownloadMode + The download mode for updates. Valid values are 0, 1, and 3. This is the setting in Windows Update settings -> Advanced options -> Delivery Optimization -> Allow downloads from other PCs. + +.PARAMETER DownloadRateBackgroundBps + The background download rate in bits per second. + +.PARAMETER DownloadRateForegroundBps + The foreground download rate in bits per second. + +.PARAMETER DownloadRateBackgroundPct + The background download rate as a percentage. + +.PARAMETER DownloadRateForegroundPct + The foreground download rate as a percentage. + +.PARAMETER UploadLimitGBMonth + The upload limit in gigabytes per month. + +.PARAMETER UpRatePctBandwidth + The upload rate as a percentage of bandwidth. + +.EXAMPLE + PS C:\> Invoke-DscResource -Name WindowsUpdate -Method Get -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Property @{} + + This command gets the current Windows Update settings. +#> [DSCResource()] class WindowsUpdate { From 059b59bd630a6f5baefb4c2922cf73f0f2135427 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 13:22:45 +0100 Subject: [PATCH 06/26] Update table values --- .../WindowsUpdate.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md b/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md index e69de29b..9fa02660 100644 --- a/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md +++ b/resources/Help/Microsoft.Windows.Setting.WindowsUpdate/WindowsUpdate.md @@ -0,0 +1,50 @@ +--- +external help file: Microsoft.Windows.Setting.Update.psm1-Help.xml +Module Name: Microsoft.Windows.Setting.Update +ms.date: 11/04/2024 +online version: +schema: 2.0.0 +title: WindowsUpdate +--- + +# WindowsUpdate + +## SYNOPSIS + +The `WindowsUpdate` DSC resource allows you to configure various Windows Update settings, including enabling or disabling specific update services, setting download and upload rates, and configuring active hours for updates. + +## DESCRIPTION + +The `WindowsUpdate` DSC resource allows you to configure various Windows Update settings, including enabling or disabling specific update services, setting download and upload rates, and configuring active hours for updates. + +## PARAMETERS + +| **Parameter** | **Attribute** | **DataType** | **Description** | **Allowed Values** | +| -------------------------------------------------- | ------------- | ------------ | -------------------------------------------------------------------------------- | ----------------------------------------------- | +| `SID` | Key | String | The security identifier. This is a key property and should not be set manually. | N/A | +| `IsContinuousInnovationOptedIn` | Optional | Boolean | Indicates whether the device is opted in to continuous innovation updates. | `$true`, `$false` | +| `AllowMUUpdateService` | Optional | Boolean | Allows updates from Microsoft Update service. | `$true`, `$false` | +| `IsExpedited` | Optional | Boolean | Indicates whether updates should be expedited. | `$true`, `$false` | +| `AllowAutoWindowsUpdateDownloadOverMeteredNetwork` | Optional | Boolean | Allows automatic Windows Update downloads over metered networks. | `$true`, `$false` | +| `RestartNotificationsAllowed` | Optional | Boolean | Allows restart notifications for updates. | `$true`, `$false` | +| `SmartActiveHoursState` | Optional | String | Configures smart active hours state for updates. | `Enabled`, `Disabled` | +| `UserChoiceActiveHoursEnd` | Optional | Integer | Specifies the end time for user-chosen active hours in `HH:MM` format. | Any valid time in `HH:MM` format | +| `UserChoiceActiveHoursStart` | Optional | Integer | Specifies the start time for user-chosen active hours in `HH:MM` format. | Any valid time in `HH:MM` format | +| `DownloadMode` | Optional | Integer | Specifies the download mode for updates. | `Foreground`, `Background`, `Bypass`, `None` | +| `DownloadRateBackgroundBps` | Optional | Integer | Specifies the background download rate for updates in Bps. | Any positive integer value. E.g. 20000 is 2MBPs | +| `DownloadRateForegroundBps` | Optional | Integer | Specifies the foreground download rate for updates in Bps. | Any positive integer value | +| `DownloadRateBackgroundPct` | Optional | Integer | Specifies the background download rate for updates as a percentage of bandwidth. | 0-100 | +| `DownloadRateForegroundPct` | Optional | Integer | Specifies the foreground download rate for updates as a percentage of bandwidth. | 0-100 | +| `UploadLimitGBMonth` | Optional | Integer | Specifies the upload limit for updates in GB per month. | 5-500 | +| `UpRatePctBandwidth` | Optional | Integer | Specifies the upload rate as a percentage of bandwidth. | 0-100 | + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +$params = @{} +Invoke-DscResource -Name WindowsUpdate -Method Set -Property $params -ModuleName Microsoft.Windows.Setting.WindowsUpdate + +# This command gets the current Windows Update settings. +``` From 4541bb4a8011900c08d9072aa8f2e44ffc5b869d Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Mon, 4 Nov 2024 14:12:57 +0100 Subject: [PATCH 07/26] Remove duplicate code --- ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 256 ++++-------------- 1 file changed, 48 insertions(+), 208 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index a20b9e4f..f26d1e87 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -1,5 +1,8 @@ $global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -$global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' # The network service account using wmiprvse.exe sets values in the user hive +# The network service account using wmiprvse.exe sets values in the user hive. This is the path to the Delivery Optimization settings in the user hive. +# It requires elevation to read the values +# Other settings might be needed e.g. DownloadRateForegroundProvider, DownloadRateBackgroundProvider +$global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' #region Functions function DoesRegistryKeyPropertyExist @@ -149,6 +152,37 @@ function Assert-DownloadRate } } } + +function Initialize-WindowsUpdate +{ + $class = [WindowsUpdate]::new() + + $hiddenProperties = $class | Get-Member -Static -Force | Where-Object { $_.MemberType -eq 'Property' } | Select-Object -ExpandProperty Name + + foreach ($p in $hiddenProperties) + { + $classPropertyName = $p.Replace("Property", "") + $dataType = $class | Get-Member | Where-Object { $_.Name -eq $classPropertyName } | Select-Object -ExpandProperty Definition | Select-String -Pattern '\[.*\]' | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value + + $currentValue = [WindowsUpdate]::GetRegistryValue($class::$p) + if ($null -eq $currentValue) + { + if ($dataType -eq '[bool]') + { + $currentValue = $false + } + + if ($dataType -eq '[int]') + { + $currentValue = 0 + } + } + + $class.$classPropertyName = $currentValue + } + + return $class +} #endregion Functions #region Classes @@ -280,22 +314,7 @@ class WindowsUpdate [WindowsUpdate] Get() { - $currentState = [WindowsUpdate]::new() - $currentState.IsContinuousInnovationOptedIn = [WindowsUpdate]::GetIsContinuousInnovationOptedInStatus() - $currentState.AllowMUUpdateService = [WindowsUpdate]::AllowMUUpdateServiceStatus() - $currentState.IsExpedited = [WindowsUpdate]::IsExpeditedStatus() - $currentState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork = [WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkStatus() - $currentState.RestartNotificationsAllowed = [WindowsUpdate]::RestartNotificationsAllowedStatus() - $currentState.SmartActiveHoursState = [WindowsUpdate]::SmartActiveHoursStateStatus() - $currentState.UserChoiceActiveHoursEnd = [WindowsUpdate]::UserChoiceActiveHoursEndStatus() - $currentState.UserChoiceActiveHoursStart = [WindowsUpdate]::UserChoiceActiveHoursStartStatus() - $currentState.DownloadMode = [WindowsUpdate]::DownloadModeStatus() - $currentState.DownloadRateBackgroundBps = [WindowsUpdate]::DownloadRateBackGroundBps() - $currentState.DownloadRateForegroundBps = [WindowsUpdate]::DownloadRateForegroundBps() - $currentState.DownloadRateBackgroundPct = [WindowsUpdate]::DownloadRateBackgroundPctStatus() - $currentState.DownloadRateForegroundPct = [WindowsUpdate]::DownloadRateForegroundPctStatus() - $currentState.UploadLimitGBMonth = [WindowsUpdate]::UploadLimitGBMonthStatus() - $currentState.UpRatePctBandwidth = [WindowsUpdate]::UpRatePctBandwidthStatus() + $currentState = Initialize-WindowsUpdate return $currentState } @@ -322,201 +341,22 @@ class WindowsUpdate } #region WindowsUpdate helper functions - static [bool] GetIsContinuousInnovationOptedInStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsContinuousInnovationOptedInProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsContinuousInnovationOptedInProperty).IsContinuousInnovationOptedInProperty - return $value - } - } - - static [bool] AllowMUUpdateServiceStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowMUUpdateServiceProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowMUUpdateServiceProperty).AllowMUUpdateServiceProperty - return $value - } - } - - static [bool] IsExpeditedStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsExpeditedProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::IsExpeditedProperty).IsExpeditedProperty - return $value - } - } - - static [bool] AllowAutoWindowsUpdateDownloadOverMeteredNetworkStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty).AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty - return $value - } - } - - static [bool] RestartNotificationsAllowedStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::RestartNotificationsAllowedProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::RestartNotificationsAllowedProperty).RestartNotificationsAllowed - return $value - } - } - - static [bool] SmartActiveHoursStateStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::SmartActiveHoursStateProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::SmartActiveHoursStateProperty).SmartActiveHoursState - return $value - } - } - - static [int] UserChoiceActiveHoursEndStatus() + static [object] GetRegistryValue($PropertyName) { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursEndProperty))) + $value = $null + if ($null -ne $PropertyName) { - return $false - } - else - { - # there is some weird behaviour with integers in the registry, so we need to get the value from the property - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursEndProperty) | Select-Object -ExpandProperty UserChoiceActiveHoursEnd - - return $value - } - } - - static [int] UserChoiceActiveHoursStartStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursStartProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name ([WindowsUpdate]::UserChoiceActiveHoursStartProperty) | Select-Object -ExpandProperty UserChoiceActiveHoursStart - return $value - } - } - - static [int] DownloadModeStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadModeProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadModeProperty) | Select-Object -ExpandProperty DownloadMode - return $value - } - } - - static [int] DownloadRateBackGroundBps() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackGroundBpsProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackGroundBpsProperty) | Select-Object -ExpandProperty DownloadRateBackGroundBps - return $value - } - } - - static [int] DownloadRateForegroundBps() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundBpsProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundBpsProperty) | Select-Object -ExpandProperty DownloadRateForegroundBps - return $value - } - } - - static [int] DownloadRateBackgroundPctStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackgroundPctProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateBackgroundPctProperty) | Select-Object -ExpandProperty DownloadRateBackgroundPct - return $value - } - } - - static [int] DownloadRateForegroundPctStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundPctProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::DownloadRateForegroundPctProperty) | Select-Object -ExpandProperty DownloadRateForegroundPct - return $value - } - } - - static [int] UploadLimitGBMonthStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UploadLimitGBMonthProperty))) - { - return $false + if ((DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name $PropertyName)) + { + $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name $PropertyName | Select-Object -ExpandProperty $PropertyName + } + elseif ((DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name $PropertyName)) + { + $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name $PropertyName | Select-Object -ExpandProperty $PropertyName + } } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UploadLimitGBMonthProperty) | Select-Object -ExpandProperty UploadLimitGBMonth - return $value - } - } - static [int] UpRatePctBandwidthStatus() - { - if (-not(DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UpRatePctBandwidthProperty))) - { - return $false - } - else - { - $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name ([WindowsUpdate]::UpRatePctBandwidthProperty) | Select-Object -ExpandProperty UpRatePctBandwidth - return $value - } + return $value } [hashtable] GetParameters() From 102c763c177fd55e720d71c0daf4a4ed2d179422 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Tue, 5 Nov 2024 02:35:45 +0100 Subject: [PATCH 08/26] Add whatif tests --- resources/PythonPip3Dsc/PythonPip3Dsc.psm1 | 34 ++++++++++----------- tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 | 27 ++++++++++++++++ 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 index 4e884164..cb15fc6c 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 @@ -15,10 +15,7 @@ function Invoke-Process [Parameter()] [ValidateNotNullOrEmpty()] - [string]$ArgumentList, - - [ValidateSet("Full", "StdOut", "StdErr", "ExitCode", "None")] - [string]$DisplayLevel + [string]$ArgumentList ) try @@ -36,6 +33,7 @@ function Invoke-Process $p.Start() | Out-Null $stOut = @() + # using ReadLine() instead of ReadToEnd() for building array object. ReadToEnd() gave different output than ReadLine() in some cases. while (-not $p.StandardOutput.EndOfStream) { $stOut += $p.StandardOutput.ReadLine() @@ -58,20 +56,12 @@ function Invoke-Process $p.WaitForExit() - if (-not([string]::IsNullOrEmpty($DisplayLevel))) - { - switch ($DisplayLevel) - { - "Full" { return $result; break } - "StdOut" { return $result.StdOut; break } - "StdErr" { return $result.StdErr; break } - "ExitCode" { return $result.ExitCode; break } - } - } + return $result } catch { - exit 1 + Write-Verbose -Message "Error occurred while executing the command: $FilePath $ArgumentList. Error:" + Write-Verbose -Message $stErr } } @@ -203,9 +193,11 @@ function Invoke-Pip3Install [Parameter()] [string]$Version, + # not explicitly used, only to call from lower functions if parameters are passed [Parameter()] [switch]$IsUpdate, + # not explicitly used, only to call from lower functions if parameters are passed [Parameter()] [switch]$DryRun ) @@ -335,11 +327,11 @@ function Invoke-Pip3 if ($global:usePip3Exe) { - return Invoke-Process -FilePath $global:pip3ExePath -ArgumentList $command -DisplayLevel Full + return Invoke-Process -FilePath $global:pip3ExePath -ArgumentList $command } else { - return Invoke-Process -FilePath pip3 -ArgumentList $command -DisplayLevel Full + return Invoke-Process -FilePath pip3 -ArgumentList $command } } @@ -491,10 +483,16 @@ class Pip3Package { $whatIfState = Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments -DryRun + $whatIfResult = $whatIfState.StdOut + if ($whatIfState.ExitCode -ne 0) + { + $whatIfResult = $whatIfState.StdErr + } + $out = @{ PackageName = $this.PackageName _metaData = @{ - whatIf = $whatIfState.StdOut + whatIf = $whatIfResult } } } diff --git a/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 b/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 index 339e6221..121400b9 100644 --- a/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 +++ b/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 @@ -104,4 +104,31 @@ Describe 'Pip3Package' { $finalState = Invoke-DscResource -Name Pip3Package -ModuleName PythonPip3Dsc -Method Get -Property $desiredState $finalState.Exist | Should -BeFalse } + + It 'Performs whatif operation successfully' -Skip:(!$IsWindows) { + $whatIfState = @{ + PackageName = 'itsdangerous' + Version = '2.2.0' + } + + $pipPackage = [Pip3Package]$whatIfState + $whatIf = $pipPackage.WhatIf() | ConvertFrom-Json + + + $whatIf.PackageName | Should -Be 'itsdangerous' + $whatIf._metaData.whatIf | Should -Contain "Would install itsdangerous-$($whatIfState.Version)" + } + + It 'Does not return whatif result if package is invalid' -Skip:(!$IsWindows) { + $whatIfState = @{ + PackageName = 'itsdangerouss' + } + + $pipPackage = [Pip3Package]$whatIfState + $whatIf = $pipPackage.WhatIf() | ConvertFrom-Json + + + $whatIf.PackageName | Should -Be 'itsdangerouss' + $whatIf._metaData.whatIf | Should -Contain "ERROR: No matching distribution found for $($whatIfState.PackageName)" + } } From 48877f6141117d099f3f48d82b8a47f7bdb8c696 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Tue, 5 Nov 2024 17:07:11 +0100 Subject: [PATCH 09/26] Comments --- .../Microsoft.Windows.Setting.WindowsUpdate.psd1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 index 7636d788..d044afd4 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psd1 @@ -69,16 +69,16 @@ # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = '*' + # FunctionsToExport = '*' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - CmdletsToExport = '*' + # CmdletsToExport = '*' # Variables to export from this module - VariablesToExport = '*' + # VariablesToExport = '*' # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - AliasesToExport = '*' + # AliasesToExport = '*' # DSC resources to export from this module DscResourcesToExport = @('WindowsUpdate') From d816dc3cc0e5b82f9c2ea7e42f48e7f10c473261 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 02:56:12 +0100 Subject: [PATCH 10/26] Re-add Invoke-Process --- resources/PythonPip3Dsc/PythonPip3Dsc.psm1 | 59 +++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 index 6a0f8053..0f900faf 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 @@ -4,6 +4,61 @@ using namespace System.Collections.Generic #region Functions +function Invoke-Process { + [CmdletBinding()] + param + ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$FilePath, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [string]$ArgumentList + ) + + try { + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $FilePath + $pinfo.RedirectStandardError = $true + $pinfo.RedirectStandardOutput = $true + $pinfo.UseShellExecute = $false + $pinfo.WindowStyle = 'Hidden' + $pinfo.CreateNoWindow = $true + $pinfo.Arguments = $ArgumentList + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + + $stOut = @() + # using ReadLine() instead of ReadToEnd() for building array object. ReadToEnd() gave different output than ReadLine() in some cases. + while (-not $p.StandardOutput.EndOfStream) { + $stOut += $p.StandardOutput.ReadLine() + } + + $stErr = @() + while (-not $p.StandardError.EndOfStream) { + $stErr += $p.StandardError.ReadLine() + } + + $result = [pscustomobject]@{ + Title = ($MyInvocation.MyCommand).Name + Command = $FilePath + Arguments = $ArgumentList + StdOut = $stOut + StdErr = $stErr + ExitCode = $p.ExitCode + } + + $p.WaitForExit() + + return $result + } catch { + Write-Verbose -Message "Error occurred while executing the command: $FilePath $ArgumentList. Error:" + Write-Verbose -Message $stErr + } +} + function Get-Pip3Path { if ($IsWindows) { # Note: When installing 64-bit version, the registry key: HKLM:\SOFTWARE\Wow6432Node\Python\PythonCore\*\InstallPath was empty. @@ -226,9 +281,9 @@ function Invoke-Pip3 { ) if ($global:usePip3Exe) { - return Start-Process -FilePath $global:pip3ExePath -ArgumentList $command -Wait -PassThru -WindowStyle Hidden + return Invoke-Process -FilePath $global:pip3ExePath -ArgumentList $command } else { - return Start-Process -FilePath pip3 -ArgumentList $command -Wait -PassThru -WindowStyle hidden + return Invoke-Process -FilePath pip3 -ArgumentList $command } } From cd15ed2399edc0720b9f92ed8bfaf9f586f1fc2f Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 02:59:21 +0100 Subject: [PATCH 11/26] Fix spelling --- .github/actions/spelling/allow.txt | 3 +++ resources/PythonPip3Dsc/PythonPip3Dsc.psm1 | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index b5196fda..ed7635cc 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -37,3 +37,6 @@ uilt Windo ELSPROBLEMS requ +whatif +pscustomobject +itsdangerouss \ No newline at end of file diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 index 0f900faf..f9c22099 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 @@ -205,7 +205,7 @@ function Invoke-Pip3Uninstall { $command.Add((Get-PackageNameWithVersion @PSBoundParameters)) $command.Add($Arguments) - # '--yes' is needed to ignore confrimation required for uninstalls + # '--yes' is needed to ignore conformation required for uninstalls $command.Add('--yes') return Invoke-Pip3 -command $command } From 6370252f94c386829730170a24680849545c46f6 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 03:02:02 +0100 Subject: [PATCH 12/26] Spell checker test --- .github/actions/spelling/allow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index ed7635cc..7cc76280 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -39,4 +39,5 @@ ELSPROBLEMS requ whatif pscustomobject +itsdangerouss itsdangerouss \ No newline at end of file From 7ed2c35be6b4dd7a3dceebdde8f511e35d16446f Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 03:07:53 +0100 Subject: [PATCH 13/26] Add attributes for validation --- ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 156 +++++------------- 1 file changed, 37 insertions(+), 119 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index f26d1e87..375ee96a 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -5,8 +5,7 @@ $global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Se $global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' #region Functions -function DoesRegistryKeyPropertyExist -{ +function DoesRegistryKeyPropertyExist { param ( [Parameter(Mandatory)] [string]$Path, @@ -20,8 +19,7 @@ function DoesRegistryKeyPropertyExist return $null -ne $itemProperty } -function Test-WindowsUpdateRegistryKey -{ +function Test-WindowsUpdateRegistryKey { param ( [Parameter(Mandatory)] [hashtable] $RegistryKeyProperty, @@ -31,11 +29,9 @@ function Test-WindowsUpdateRegistryKey ) $result = $true - foreach ($key in $RegistryKeyProperty.Keys) - { + foreach ($key in $RegistryKeyProperty.Keys) { $value = $RegistryKeyProperty[$key] - if ($value -ne $CurrentState.$key) - { + if ($value -ne $CurrentState.$key) { $result = $false } } @@ -43,8 +39,7 @@ function Test-WindowsUpdateRegistryKey return $result } -function Set-WindowsUpdateRegistryKey -{ +function Set-WindowsUpdateRegistryKey { param ( [Parameter(Mandatory)] [string]$Path, @@ -54,32 +49,19 @@ function Set-WindowsUpdateRegistryKey [hashtable] $RegistryKeyProperty ) - if (-not (Test-Path -Path $Path)) - { + if (-not (Test-Path -Path $Path)) { $null = New-Item -Path $Path -Force } - foreach ($key in $RegistryKeyProperty.Keys) - { + foreach ($key in $RegistryKeyProperty.Keys) { $value = $RegistryKeyProperty[$key] $typeInfo = $value.GetType().Name - if ($typeInfo -eq 'Boolean') - { + if ($typeInfo -eq 'Boolean') { $value = [int]$value } - # validate the value of UserChoiceActiveHoursEnd and UserChoiceActiveHoursStart to be between 0 and 24 - Assert-UserChoiceValue -KeyName $key -Value $value - - # validate the value of DownloadRateBackgroundPct, DownloadRateForegroundPct and UpRatePctBandwith to be between 0 and 100 - Assert-RatePercentageValue -KeyName $key -Value $value - - # validate the value of UpRatePctBandwith to be between 5 and 500 - Assert-UpRateValue -KeyName $key -Value $value - - if (-not (DoesRegistryKeyPropertyExist -Path $Path -Name $key)) - { + if (-not (DoesRegistryKeyPropertyExist -Path $Path -Name $key)) { $null = New-ItemProperty -Path $Path -Name $key -Value $value -PropertyType 'DWord' -Force } @@ -88,92 +70,35 @@ function Set-WindowsUpdateRegistryKey } } -function Assert-UpRateValue -{ - param ( - [Parameter(Mandatory)] - [string] $KeyName, - - [Parameter(Mandatory)] - [int] $Value - ) - - if ($KeyName -eq 'UpRatePctBandwidth' -and $Value -notin (5..500)) - { - Throw "You are specifying a percentage value, which must be between 5 and 500. The value you provided is $Value. Please provide a value between 5 and 500." - } -} - -function Assert-RatePercentageValue -{ - param ( - [Parameter(Mandatory)] - [string] $KeyName, - - [Parameter(Mandatory)] - [int] $Value - ) - - if ($KeyName -in ('DownloadRateBackgroundPct', 'DownloadRateForegroundPct', 'UpRatePctBandwidth') -and $Value -notin (0..100)) - { - # TODO: It might be beneficial to add `Reasons` and not throw, only return statement - Throw "You are specifying a percentage value, which must be between 0 and 100. The value you provided is $Value. Please provide a value between 0 and 100." - } -} - -function Assert-UserChoiceValue -{ - param ( - [Parameter(Mandatory)] - [string] $KeyName, - - [Parameter(Mandatory)] - [int] $Value - ) - - if ($KeyName -in ('UserChoiceActiveHoursEnd', 'UserChoiceActiveHoursStart') -and $Value -notin (0..24)) - { - Throw "Value must be between 0 and 24" - } -} - -function Assert-DownloadRate -{ +function Assert-DownloadRate { param ( [Parameter(Mandatory)] [hashtable] $Parameters ) - if ($Parameters.ContainsKey('DownloadRateBackgroundPct') -or $Parameters.ContainsKey('DownloadRateForegroundPct')) - { - if ($Parameters.ContainsKey('DownloadRateBackgroundBps') -or $Parameters.ContainsKey('DownloadRateForegroundBps')) - { - Throw "Cannot set both DownloadRateBackgroundPct/DownloadRateForegroundPct and DownloadRateBackgroundBps/DownloadRateForegroundBps" + if ($Parameters.ContainsKey('DownloadRateBackgroundPct') -or $Parameters.ContainsKey('DownloadRateForegroundPct')) { + if ($Parameters.ContainsKey('DownloadRateBackgroundBps') -or $Parameters.ContainsKey('DownloadRateForegroundBps')) { + Throw 'Cannot set both DownloadRateBackgroundPct/DownloadRateForegroundPct and DownloadRateBackgroundBps/DownloadRateForegroundBps' } } } -function Initialize-WindowsUpdate -{ +function Initialize-WindowsUpdate { $class = [WindowsUpdate]::new() $hiddenProperties = $class | Get-Member -Static -Force | Where-Object { $_.MemberType -eq 'Property' } | Select-Object -ExpandProperty Name - foreach ($p in $hiddenProperties) - { - $classPropertyName = $p.Replace("Property", "") + foreach ($p in $hiddenProperties) { + $classPropertyName = $p.Replace('Property', '') $dataType = $class | Get-Member | Where-Object { $_.Name -eq $classPropertyName } | Select-Object -ExpandProperty Definition | Select-String -Pattern '\[.*\]' | Select-Object -ExpandProperty Matches | Select-Object -ExpandProperty Value $currentValue = [WindowsUpdate]::GetRegistryValue($class::$p) - if ($null -eq $currentValue) - { - if ($dataType -eq '[bool]') - { + if ($null -eq $currentValue) { + if ($dataType -eq '[bool]') { $currentValue = $false } - if ($dataType -eq '[int]') - { + if ($dataType -eq '[int]') { $currentValue = 0 } } @@ -244,8 +169,7 @@ function Initialize-WindowsUpdate This command gets the current Windows Update settings. #> [DSCResource()] -class WindowsUpdate -{ +class WindowsUpdate { # Key required. Do not set. [DscProperty(Key)] [string] $SID @@ -269,9 +193,11 @@ class WindowsUpdate [nullable[bool]] $SmartActiveHoursState [DscProperty()] + [ValidateRange(0, 24)] [nullable[int]] $UserChoiceActiveHoursEnd [DscProperty()] + [ValidateRange(0, 24)] [nullable[int]] $UserChoiceActiveHoursStart [DscProperty()] @@ -285,15 +211,19 @@ class WindowsUpdate [nullable[int]] $DownloadRateForegroundBps [DscProperty()] + [ValidateRange(0, 100)] [nullable[int]] $DownloadRateBackgroundPct [DscProperty()] + [ValidateRange(0, 100)] [nullable[int]] $DownloadRateForegroundPct [DscProperty()] + [ValidateRange(5, 500)] [nullable[int]] $UploadLimitGBMonth [DscProperty()] + [ValidateRange(0, 100)] [nullable[int]] $UpRatePctBandwidth static hidden [string] $IsContinuousInnovationOptedInProperty = 'IsContinuousInnovationOptedIn' @@ -312,24 +242,20 @@ class WindowsUpdate static hidden [string] $UploadLimitGBMonthProperty = 'UploadLimitGBMonth' static hidden [string] $UpRatePctBandwidthProperty = 'UpRatePctBandwidth' - [WindowsUpdate] Get() - { + [WindowsUpdate] Get() { $currentState = Initialize-WindowsUpdate return $currentState } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() $settableProperties = $this.GetParameters() return (Test-WindowsUpdateRegistryKey -RegistryKeyProperty $settableProperties -CurrentState $currentState) } - [void] Set() - { - if ($this.Test()) - { + [void] Set() { + if ($this.Test()) { return } @@ -341,17 +267,12 @@ class WindowsUpdate } #region WindowsUpdate helper functions - static [object] GetRegistryValue($PropertyName) - { + static [object] GetRegistryValue($PropertyName) { $value = $null - if ($null -ne $PropertyName) - { - if ((DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name $PropertyName)) - { + if ($null -ne $PropertyName) { + if ((DoesRegistryKeyPropertyExist -Path $global:WindowsUpdateSettingPath -Name $PropertyName)) { $value = Get-ItemProperty -Path $global:WindowsUpdateSettingPath -Name $PropertyName | Select-Object -ExpandProperty $PropertyName - } - elseif ((DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name $PropertyName)) - { + } elseif ((DoesRegistryKeyPropertyExist -Path $global:DeliveryOptimizationSettingPath -Name $PropertyName)) { $value = Get-ItemProperty -Path $global:DeliveryOptimizationSettingPath -Name $PropertyName | Select-Object -ExpandProperty $PropertyName } } @@ -359,13 +280,10 @@ class WindowsUpdate return $value } - [hashtable] GetParameters() - { + [hashtable] GetParameters() { $parameters = @{} - foreach ($property in $this.PSObject.Properties) - { - if (-not ([string]::IsNullOrEmpty($property.Value))) - { + foreach ($property in $this.PSObject.Properties) { + if (-not ([string]::IsNullOrEmpty($property.Value))) { $parameters[$property.Name] = $property.Value } } From 6dfddf3106f3f8706a150f1499d8fc99c7a85d02 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 03:09:24 +0100 Subject: [PATCH 14/26] Spell checker words --- .github/actions/spelling/allow.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 7cc76280..d3f605b8 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -40,4 +40,7 @@ requ whatif pscustomobject itsdangerouss -itsdangerouss \ No newline at end of file +itsdangerouss +Bandwith +PCs +wmiprvse \ No newline at end of file From 8d8ad1207719234e34173ec80ab27e7b2d2ff824 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 03:11:09 +0100 Subject: [PATCH 15/26] Test --- .github/actions/spelling/allow.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index d3f605b8..3008b89c 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -43,4 +43,4 @@ itsdangerouss itsdangerouss Bandwith PCs -wmiprvse \ No newline at end of file +wmiprvse From 1fbe8e21c915faa40d56570328fed4c364fbc38c Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Wed, 6 Nov 2024 03:12:24 +0100 Subject: [PATCH 16/26] Add line break for test --- .github/actions/spelling/allow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 3008b89c..ed8ea9bc 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -44,3 +44,4 @@ itsdangerouss Bandwith PCs wmiprvse + From 60fa17ea98b02906fe15db509d289b60633ce371 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Thu, 14 Nov 2024 15:46:44 +0100 Subject: [PATCH 17/26] Fix variables at top --- ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index 375ee96a..c41ae3fd 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -1,8 +1,12 @@ -$global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' -# The network service account using wmiprvse.exe sets values in the user hive. This is the path to the Delivery Optimization settings in the user hive. -# It requires elevation to read the values -# Other settings might be needed e.g. DownloadRateForegroundProvider, DownloadRateBackgroundProvider -$global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' +if ([string]::IsNullOrEmpty($env:TestRegistryPath)) { + $global:WindowsUpdateSettingPath = 'HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings' + # The network service account using wmiprvse.exe sets values in the user hive. This is the path to the Delivery Optimization settings in the user hive. + # It requires elevation to read the values + # Other settings might be needed e.g. DownloadRateForegroundProvider, DownloadRateBackgroundProvider + $global:DeliveryOptimizationSettingPath = 'Registry::HKEY_USERS\S-1-5-20\Software\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Settings' +} else { + $global:WindowsUpdateSettingPath = $global:DeliveryOptimizationSettingPath = $env:TestRegistryPath +} #region Functions function DoesRegistryKeyPropertyExist { @@ -62,13 +66,13 @@ function Set-WindowsUpdateRegistryKey { } if (-not (DoesRegistryKeyPropertyExist -Path $Path -Name $key)) { - $null = New-ItemProperty -Path $Path -Name $key -Value $value -PropertyType 'DWord' -Force + $null = New-ItemProperty -Path $Path -Name $key -Value $value -PropertyType 'DWord' -Force } Write-Verbose -Message "Setting $key to $($RegistryKeyProperty[$key])" Set-ItemProperty -Path $Path -Name $key -Value $value } -} +} function Assert-DownloadRate { param ( @@ -102,7 +106,7 @@ function Initialize-WindowsUpdate { $currentValue = 0 } } - + $class.$classPropertyName = $currentValue } @@ -171,9 +175,9 @@ function Initialize-WindowsUpdate { [DSCResource()] class WindowsUpdate { # Key required. Do not set. - [DscProperty(Key)] + [DscProperty(Key)] [string] $SID - + [DscProperty()] [nullable[bool]] $IsContinuousInnovationOptedIn @@ -221,7 +225,7 @@ class WindowsUpdate { [DscProperty()] [ValidateRange(5, 500)] [nullable[int]] $UploadLimitGBMonth - + [DscProperty()] [ValidateRange(0, 100)] [nullable[int]] $UpRatePctBandwidth @@ -244,7 +248,7 @@ class WindowsUpdate { [WindowsUpdate] Get() { $currentState = Initialize-WindowsUpdate - + return $currentState } @@ -292,4 +296,4 @@ class WindowsUpdate { } #endRegion WindowsUpdate helper functions } -#endRegion classes \ No newline at end of file +#endRegion classes From cdab3c325ffbb3b95b34ceaa4bc6b7c0700b5150 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 03:13:56 +0100 Subject: [PATCH 18/26] Revert spelling --- .github/actions/spelling/allow.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index ed8ea9bc..54f9474e 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -1,3 +1,4 @@ + admins apps appwiz @@ -11,6 +12,7 @@ codeowners github https Icm +markdownlint microsoft msftbot numpy @@ -39,9 +41,6 @@ ELSPROBLEMS requ whatif pscustomobject -itsdangerouss -itsdangerouss -Bandwith -PCs -wmiprvse - +VGpu +wildcards +worktree From b371c1ef7e9a381a853b55bbcdcafee32f1909e3 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 03:14:57 +0100 Subject: [PATCH 19/26] Resolve conflict --- resources/PythonPip3Dsc/PythonPip3Dsc.psm1 | 28 +++++++--------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 index f9c22099..cd627440 100644 --- a/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 +++ b/resources/PythonPip3Dsc/PythonPip3Dsc.psm1 @@ -38,7 +38,7 @@ function Invoke-Process { $stErr = @() while (-not $p.StandardError.EndOfStream) { - $stErr += $p.StandardError.ReadLine() + $stErr += $p.StandardError.ReadLine() } $result = [pscustomobject]@{ @@ -132,16 +132,8 @@ function Get-PackageNameWithVersion { [string]$PackageName, [Parameter(Mandatory = $false)] - [string]$Arguments, - - [Parameter(Mandatory = $false)] - [string]$Version, - - [Parameter()] - [switch]$IsUpdate, - - [Parameter()] - [switch]$DryRun + [AllowNull()] + [string]$Version ) if ($PSBoundParameters.ContainsKey('Version') -and -not ([string]::IsNullOrEmpty($Version))) { @@ -162,18 +154,16 @@ function Invoke-Pip3Install { [Parameter()] [string]$Version, - # not explicitly used, only to call from lower functions if parameters are passed [Parameter()] [switch]$IsUpdate, - # not explicitly used, only to call from lower functions if parameters are passed [Parameter()] [switch]$DryRun ) $command = [List[string]]::new() $command.Add('install') - $command.Add((Get-PackageNameWithVersion @PSBoundParameters)) + $command.Add((Get-PackageNameWithVersion -PackageName $PackageName -Version $Version)) if ($IsUpdate.IsPresent) { $command.Add('--force-reinstall') } @@ -184,7 +174,7 @@ function Invoke-Pip3Install { $command.Add($Arguments) Write-Verbose -Message "Executing 'pip' install with command: $command" $result = Invoke-Pip3 -command $command - + return $result } @@ -202,7 +192,7 @@ function Invoke-Pip3Uninstall { $command = [List[string]]::new() $command.Add('uninstall') - $command.Add((Get-PackageNameWithVersion @PSBoundParameters)) + $command.Add((Get-PackageNameWithVersion -PackageName $PackageName -Version $Version)) $command.Add($Arguments) # '--yes' is needed to ignore conformation required for uninstalls @@ -409,7 +399,7 @@ class Pip3Package { } } - [string] WhatIf() { + [string] WhatIf() { if ($this.Exist) { $whatIfState = Invoke-Pip3Install -PackageName $this.PackageName -Version $this.Version -Arguments $this.Arguments -DryRun @@ -428,7 +418,7 @@ class Pip3Package { # Uninstall does not have --dry-run param $out = @{} } - + return ($out | ConvertTo-Json -Depth 10 -Compress) } @@ -464,4 +454,4 @@ class Pip3Package { #endRegion Pip3Package Helper functions } -#endregion DSCResources \ No newline at end of file +#endregion DSCResources From 041076af34ab3a6cdaf476cc09b33ff9aee645fb Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 03:16:19 +0100 Subject: [PATCH 20/26] Commit conflic --- tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 b/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 index b10683ed..425340a4 100644 --- a/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 +++ b/tests/PythonPip3Dsc/PythonPip3Dsc.Tests.ps1 @@ -108,11 +108,18 @@ Describe 'Pip3Package' { $whatIfState = @{ PackageName = 'itsdangerous' Version = '2.2.0' + Exist = $false } $pipPackage = [Pip3Package]$whatIfState + + # Uninstall to make sure it is not present + $pipPackage.Set() + + $pipPackage.Exist = $true + + # Call whatif to see if it "will" install $whatIf = $pipPackage.WhatIf() | ConvertFrom-Json - $whatIf.PackageName | Should -Be 'itsdangerous' $whatIf._metaData.whatIf | Should -Contain "Would install itsdangerous-$($whatIfState.Version)" @@ -120,14 +127,14 @@ Describe 'Pip3Package' { It 'Does not return whatif result if package is invalid' -Skip:(!$IsWindows) { $whatIfState = @{ - PackageName = 'itsdangerouss' + PackageName = 'invalidPackageName' } $pipPackage = [Pip3Package]$whatIfState $whatIf = $pipPackage.WhatIf() | ConvertFrom-Json - - $whatIf.PackageName | Should -Be 'itsdangerouss' + + $whatIf.PackageName | Should -Be 'invalidPackageName' $whatIf._metaData.whatIf | Should -Contain "ERROR: No matching distribution found for $($whatIfState.PackageName)" } } From e5a49f3e037715d7a75215aaf2bed36d10e4898d Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 03:54:00 +0100 Subject: [PATCH 21/26] Add tests --- ...crosoft.Windows.Setting.WindowsUpdate.psm1 | 22 ++++- ...ft.Windows.Setting.WindowsUpdate.Tests.ps1 | 98 ++++++++++++++++++- 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 index c41ae3fd..35a16767 100644 --- a/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 +++ b/resources/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.psm1 @@ -54,7 +54,7 @@ function Set-WindowsUpdateRegistryKey { ) if (-not (Test-Path -Path $Path)) { - $null = New-Item -Path $Path -Force + New-Item -Path $Path -Force | Out-Null } foreach ($key in $RegistryKeyProperty.Keys) { @@ -69,6 +69,16 @@ function Set-WindowsUpdateRegistryKey { $null = New-ItemProperty -Path $Path -Name $key -Value $value -PropertyType 'DWord' -Force } + if ($key -eq 'UploadLimitGBMonth') { + if ($value -lt 5) { + Throw 'UploadLimitGBMonth must be greater than or equal to 5.' + } + } + + if ($key -eq 'RestartNotificationsAllowed') { + $key = 'RestartNotificationsAllowed2' + } + Write-Verbose -Message "Setting $key to $($RegistryKeyProperty[$key])" Set-ItemProperty -Path $Path -Name $key -Value $value } @@ -107,6 +117,10 @@ function Initialize-WindowsUpdate { } } + if ($classPropertyName -eq 'RestartNotificationsAllowed2') { + $classPropertyName = 'RestartNotificationsAllowed' + } + $class.$classPropertyName = $currentValue } @@ -198,10 +212,12 @@ class WindowsUpdate { [DscProperty()] [ValidateRange(0, 24)] + [AllowNull()] [nullable[int]] $UserChoiceActiveHoursEnd [DscProperty()] [ValidateRange(0, 24)] + [AllowNull()] [nullable[int]] $UserChoiceActiveHoursStart [DscProperty()] @@ -223,7 +239,7 @@ class WindowsUpdate { [nullable[int]] $DownloadRateForegroundPct [DscProperty()] - [ValidateRange(5, 500)] + [ValidateRange(0, 500)] [nullable[int]] $UploadLimitGBMonth [DscProperty()] @@ -234,7 +250,7 @@ class WindowsUpdate { static hidden [string] $AllowMUUpdateServiceProperty = 'AllowMUUpdateService' static hidden [string] $IsExpeditedProperty = 'IsExpedited' static hidden [string] $AllowAutoWindowsUpdateDownloadOverMeteredNetworkProperty = 'AllowAutoWindowsUpdateDownloadOverMeteredNetwork' - static hidden [string] $RestartNotificationsAllowedProperty = 'RestartNotificationsAllowed2' + static hidden [string] $RestartNotificationsAllowed2Property = 'RestartNotificationsAllowed2' static hidden [string] $SmartActiveHoursStateProperty = 'SmartActiveHoursState' static hidden [string] $UserChoiceActiveHoursEndProperty = 'UserChoiceActiveHoursEnd' static hidden [string] $UserChoiceActiveHoursStartProperty = 'UserChoiceActiveHoursStart' diff --git a/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 b/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 index 229d2bce..1395bfe0 100644 --- a/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 +++ b/tests/Microsoft.Windows.Setting.WindowsUpdate/Microsoft.Windows.Setting.WindowsUpdate.Tests.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. using module Microsoft.Windows.Setting.WindowsUpdate -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest <# @@ -12,13 +12,105 @@ Set-StrictMode -Version Latest BeforeAll { Import-Module Microsoft.Windows.Setting.WindowsUpdate + + $currentState = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Get -Property @{} + + $global:Parameters = $currentState.GetParameters() + + Write-Verbose ("Current state: `n{0}" -f $($global:Parameters | ConvertTo-Json | Out-String)) -Verbose } Describe 'List available DSC resources' { It 'Shows DSC Resources' { - $expectedDSCResources = "WindowsUpdate" + $expectedDSCResources = 'WindowsUpdate' $availableDSCResources = (Get-DscResource -Module Microsoft.Windows.Setting.WindowsUpdate).Name $availableDSCResources.count | Should -Be 1 $availableDSCResources | Where-Object { $expectedDSCResources -notcontains $_ } | Should -BeNullOrEmpty -ErrorAction Stop } -} \ No newline at end of file +} + +Describe 'WindowsUpdate' { + It 'Keeps current value.' { + $initialState = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Get -Property @{} + + $parameters = $initialState.GetParameters() + + $testResult = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Test -Property $parameters + $testResult.InDesiredState | Should -Be $true + + # Invoking set should not change these values. + Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Set -Property $parameters + $finalState = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Get -Property @{} + $finalState.IsContinuousInnovationOptedIn | Should -Be $initialState.IsContinuousInnovationOptedIn + $finalState.AllowMUUpdateService | Should -Be $initialState.AllowMUUpdateService + $finalState.IsExpedited | Should -Be $initialState.IsExpedited + $finalState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork | Should -Be $initialState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork + $finalState.RestartNotificationsAllowed | Should -Be $initialState.RestartNotificationsAllowed + $finalState.SmartActiveHoursState | Should -Be $initialState.SmartActiveHoursState + $finalstate.UserChoiceActiveHoursEnd | Should -Be $initialState.UserChoiceActiveHoursEnd + $finalstate.UserChoiceActiveHoursStart | Should -Be $initialState.UserChoiceActiveHoursStart + $finalState.DownloadMode | Should -Be $initialState.DownloadMode + $finalState.DownloadRateBackgroundBps | Should -Be $initialState.DownloadRateBackgroundBps + $finalState.DownloadRateForegroundBps | Should -Be $initialState.DownloadRateForegroundBps + $finalstate.DownloadRateBackgroundPct | Should -Be $initialState.DownloadRateBackgroundPct + $finalstate.DownloadRateForegroundPct | Should -Be $initialState.DownloadRateForegroundPct + $finalState.UploadLimitGBMonth | Should -Be $initialState.UploadLimitGBMonth + $finalState.UpRatePctBandwidth | Should -Be $initialState.UpRatePctBandwidth + + } + + It 'Sets desired value with only background' { + $desiredState = @{ + IsContinuousInnovationOptedIn = $true + AllowMUUpdateService = $true + IsExpedited = $true + AllowAutoWindowsUpdateDownloadOverMeteredNetwork = $true + RestartNotificationsAllowed = $true + SmartActiveHoursState = 1 + UserChoiceActiveHoursEnd = 10 + UserChoiceActiveHoursStart = 2 + DownloadMode = 1 + DownloadRateBackgroundBps = 100 + DownloadRateForegroundBps = 100 + UploadLimitGBMonth = 100 + UpRatePctBandwidth = 100 + } + + Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Get -Property @{} + $finalState.IsContinuousInnovationOptedIn | Should -Be $desiredState.IsContinuousInnovationOptedIn + $finalState.AllowMUUpdateService | Should -Be $desiredState.AllowMUUpdateService + $finalState.IsExpedited | Should -Be $desiredState.IsExpedited + $finalState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork | Should -Be $desiredState.AllowAutoWindowsUpdateDownloadOverMeteredNetwork + $finalState.RestartNotificationsAllowed | Should -Be $desiredState.RestartNotificationsAllowed + $finalState.SmartActiveHoursState | Should -Be $desiredState.SmartActiveHoursState + $finalstate.UserChoiceActiveHoursEnd | Should -Be $desiredState.UserChoiceActiveHoursEnd + $finalstate.UserChoiceActiveHoursStart | Should -Be $desiredState.UserChoiceActiveHoursStart + $finalState.DownloadMode | Should -Be $desiredState.DownloadMode + $finalState.DownloadRateBackgroundBps | Should -Be $desiredState.DownloadRateBackgroundBps + $finalState.DownloadRateForegroundBps | Should -Be $desiredState.DownloadRateForegroundBps + $finalState.UploadLimitGBMonth | Should -Be $desiredState.UploadLimitGBMonth + $finalState.UpRatePctBandwidth | Should -Be $desiredState.UpRatePctBandwidth + } + + It 'Sets desired value with only background rate' { + $desiredState = @{ + DownloadRateBackgroundPct = 50 + DownloadRateForegroundPct = 100 + } + + Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Set -Property $desiredState + + $finalState = Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Get -Property @{} + $finalState.DownloadRateBackgroundPct | Should -Be $desiredState.DownloadRateBackgroundPct + $finalState.DownloadRateForegroundPct | Should -Be $desiredState.DownloadRateForegroundPct + } +} + +AfterAll { + Write-Verbose -Message 'Restoring the machine to the original state.' -Verbose + $global:Parameters.Remove('DownloadRateBackgroundPct') + $global:Parameters.Remove('DownloadRateForegroundPct') + Invoke-DscResource -Name WindowsUpdate -ModuleName Microsoft.Windows.Setting.WindowsUpdate -Method Set -Property $global:Parameters +} From 6aab6a606f9c8c84d41daadd05aec46c1d09861d Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 03:54:30 +0100 Subject: [PATCH 22/26] Fix spelling --- .github/actions/spelling/allow.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 54f9474e..08b40dc3 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -44,3 +44,5 @@ pscustomobject VGpu wildcards worktree +PCs +wmiprvse From a417fd6d2e92824dde1b524eafec312fd6bb1de5 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 15 Nov 2024 04:05:53 +0100 Subject: [PATCH 23/26] Add spelling --- .github/actions/spelling/expect/generic_terms.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/expect/generic_terms.txt b/.github/actions/spelling/expect/generic_terms.txt index 5180255f..83dee259 100644 --- a/.github/actions/spelling/expect/generic_terms.txt +++ b/.github/actions/spelling/expect/generic_terms.txt @@ -18,4 +18,5 @@ sortby ssh usr versioning -VGpu \ No newline at end of file +VGpu +finalstate From 7f95436bb4835384565d518b5daecf7ca15140e3 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 2 Dec 2024 01:05:28 +0100 Subject: [PATCH 24/26] Generic terms --- .../actions/spelling/expect/generic_terms.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/actions/spelling/expect/generic_terms.txt b/.github/actions/spelling/expect/generic_terms.txt index 4d175d5b..a2e61db5 100644 --- a/.github/actions/spelling/expect/generic_terms.txt +++ b/.github/actions/spelling/expect/generic_terms.txt @@ -21,3 +21,22 @@ versioning VGpu finalstate ADDLOCAL +aspnetcore +echarts +ephemoral +Fancyzones +gdk +Hmmss +JDK +jquery +Mfor +MMdd +NVM +reduxjs +restsource +seperated +tastejs +todomvc +videojs +vsconfig +websites \ No newline at end of file From 6ca7556890b09811b40d15939868443bca983686 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 2 Dec 2024 01:07:02 +0100 Subject: [PATCH 25/26] Spelling --- .github/actions/spelling/allow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 08b40dc3..7dca07a4 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -46,3 +46,4 @@ wildcards worktree PCs wmiprvse +websites \ No newline at end of file From 0fbd0c3377491e92ddf17fda95bb7787622227b6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 2 Dec 2024 01:09:09 +0100 Subject: [PATCH 26/26] Spelling --- .github/actions/spelling/allow.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 7dca07a4..ec1b8a7c 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -46,4 +46,4 @@ wildcards worktree PCs wmiprvse -websites \ No newline at end of file +websites