From 7c5d5cddfa775702e74ceadca02df53651384c19 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 Nov 2024 12:50:13 -0500 Subject: [PATCH 01/16] Add Microsoft.Windows.Assertion --- pipelines/azure-pipelines.yml | 4 + .../Microsoft.Windows.Assertion.psd1 | 39 ++ .../Microsoft.Windows.Assertion.psm1 | 456 ++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 create mode 100644 resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 diff --git a/pipelines/azure-pipelines.yml b/pipelines/azure-pipelines.yml index 8e619f41..f647e26d 100644 --- a/pipelines/azure-pipelines.yml +++ b/pipelines/azure-pipelines.yml @@ -80,6 +80,10 @@ extends: displayName: "Publish Pipeline Microsoft.DotNet.Dsc" targetPath: $(Build.SourcesDirectory)\resources\Microsoft.DotNet.Dsc\ artifactName: Microsoft.DotNet.Dsc + - output: pipelineArtifact + displayName: "Publish Pipeline Microsoft.Windows.Assertion" + targetPath: $(Build.SourcesDirectory)\resources\Microsoft.Windows.Assertion\ + artifactName: Microsoft.Windows.Assertion" steps: - checkout: self diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 new file mode 100644 index 00000000..007bbd06 --- /dev/null +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +@{ + RootModule = 'Microsoft.Windows.Assertion.psm1' + ModuleVersion = '0.1.0' + GUID = 'e3510ba2-cc19-4fb2-872a-a40833c30e58' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corp. All rights reserved.' + Description = 'DSC Module for ensuring the system meets certain specifications' + PowerShellVersion = '7.2' + DscResourcesToExport = @( + 'OsEditionId', + 'SystemArchitecture', + 'ProcessorArchitecture', + 'HyperVisorPresent', + 'OsInstallDate', + 'OsVersion', + 'CsManufacturer', + 'CsModel', + 'CsDomain', + 'PowerShellVersion', + 'PnPDevice' + ) + PrivateData = @{ + PSData = @{ + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @( + 'PSDscResource_EditionID', + 'PSDscResource_SystemArchitecture', + 'PSDscResource_HyperVisorPresent', + 'PSDscResource_OsInstallDate' + ) + + # Prerelease string of this module + Prerelease = 'alpha' + } + } +} diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 new file mode 100644 index 00000000..83bd5c73 --- /dev/null +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -0,0 +1,456 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +enum PnPDeviceState +{ + OK + ERROR + DEGRADED + UNKNOWN +} + +[DSCResource()] +class OsEditionId +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredEdition + + [DscProperty(NotConfigurable)] + [string] $Edition + + [OsEditionId] Get() + { + $this.Edition = Get-ComputerInfo | Select-Object -ExpandProperty WindowsEditionId + + return @{ + RequredEdition = $this.RequiredEdition + Edition = $this.Edition + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Edition -eq $currentState.RequiredEdition + } + + [void] Set() + { + # This resource is only for asserting the Edition ID requirement. + } +} + +[DSCResource()] +class SystemArchitecture +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredArchitecture + + [DscProperty(NotConfigurable)] + [string] $Architecture + + [SystemArchitecture] Get() + { + $this.Architecture = Get-ComputerInfo | Select-Object -ExpandProperty OsArchitecture + + return @{ + RequiredArchitecture = $this.RequiredArchitecture + Architecture = $this.Architecture + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Architecture -eq $currentState.RequiredArchitecture + } + + [void] Set() + { + # This resource is only for asserting the System Architecture requirement. + } +} + +[DSCResource()] +class ProcessorArchitecture +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredArchitecture + + [DscProperty(NotConfigurable)] + [string] $Architecture + + [ProcessorArchitecture] Get() + { + $this.Architecture = $env:PROCESSOR_ARCHITECTURE + + return @{ + RequiredArchitecture = $this.RequiredArchitecture + Architecture = $this.Architecture + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Architecture -eq $currentState.RequiredArchitecture + } + + [void] Set() + { + # This resource is only for asserting the System Architecture requirement. + } +} + +[DSCResource()] +class HyperVisorPresent +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [bool] $Required + + [DscProperty(NotConfigurable)] + [bool] $HyperVisorPresent + + [HyperVisorPresent] Get() + { + $this.HyperVisorPresent = Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent + + return @{ + Required = $this.Required + HyperVisorPresent = $this.HyperVisorPresent + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Required -eq $currentState.HyperVisorPresent + } + + [void] Set() + { + # This resource is only for asserting the presence of a HyperVisor. + } +} + +[DSCResource()] +class OsInstallDate +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty()] + [string] $Before = [System.DateTime]::Now + + [DscProperty()] + [string] $After + + [DscProperty(NotConfigurable)] + [string] $InstallDate + + [OsInstallDate] Get() + { + # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.Before, [ref]$parsedBefore)` is erroring + try + { + $this.Before = $this.Before ? [System.DateTimeOffset]::Parse($this.Before) : $null + } + catch + { + throw "'$($this.Before)' is not a valid Date string." + } + + # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.After, [ref]$parsedAfter)` is erroring + try + { + $this.After = $this.After ? [System.DateTimeOffset]::Parse($this.After) : $null + } + catch + { + throw "'$($this.After)' is not a valid Date string." + } + + $this.InstallDate = [System.DateTimeOffset]::Parse($(Get-ComputerInfo | Select-Object -ExpandProperty OsInstallDate)) + + return @{ + Before = $this.Before + After = $this.After + InstallDate = $this.InstallDate + } + } + + [bool] Test() + { + $currentState = $this.Get() + # this.Get() should always return [System.DateTimeOffset] or $null which can be compared directly + # $null should always be treated as less than a [System.DateTimeOffset] + return ($currentState.InstallDate -gt $currentState.After) -and ($currentState.InstallDate -lt $currentState.Before) + } + + [void] Set() + { + # This resource is only for asserting the OS Install Date. + } +} + +# This is the same function from Microsoft.Windows.Developer, just included here as it seemed to make sense +[DSCResource()] +class OsVersion +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $MinVersion + + [DscProperty(NotConfigurable)] + [string] $OsVersion + + [OsVersion] Get() + { + $parsedVersion = $null + if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) + { + throw "'$($this.MinVersion)' is not a valid Version string." + } + + $this.OsVersion = Get-ComputerInfo | Select-Object -ExpandProperty OsVersion + + return @{ + MinVersion = $this.MinVersion + OsVersion = $this.OsVersion + } + } + + [bool] Test() + { + $currentState = $this.Get() + return [System.Version]$currentState.OsVersion -ge [System.Version]$currentState.MinVersion + } + + [void] Set() + { + # This resource is only for asserting the os version requirement. + } +} + +[DSCResource()] +class CsManufacturer +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredManufacturer + + [DscProperty(NotConfigurable)] + [string] $Manufacturer + + [CsManufacturer] Get() + { + $this.Manufacturer = Get-ComputerInfo | Select-Object -ExpandProperty CsManufacturer + + return @{ + RequiredManufacturer = $this.RequiredManufacturer + Manufacturer = $this.Manufacturer + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Manufacturer -eq $currentState.RequiredManufacturer + } + + [void] Set() + { + # This resource is only for asserting the Computer Manufacturer requirement. + } +} + +[DSCResource()] +class CsModel +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredModel + + [DscProperty(NotConfigurable)] + [string] $Model + + [CsModel] Get() + { + $this.Model = Get-ComputerInfo | Select-Object -ExpandProperty CsModel + + return @{ + RequiredModel = $this.RequiredModel + Model = $this.Model + } + } + + [bool] Test() + { + $currentState = $this.Get() + return $currentState.Model -eq $currentState.RequiredModel + } + + [void] Set() + { + # This resource is only for asserting the Computer Manufacturer requirement. + } +} + +[DSCResource()] +class CsDomain +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $RequiredDomain + + [DscProperty()] + [string] $RequiredRole + + [DscProperty(NotConfigurable)] + [string] $Domain + + [DscProperty(NotConfigurable)] + [string] $Role + + [CsDomain] Get() + { + $domainInfo = Get-ComputerInfo | Select-Object -Property CsDomain, CsDomainRole + $this.Domain = $domainInfo.CsDomain + $this.Role = $domainInfo.CsDomainRole + + return @{ + RequiredDomain = $this.RequiredDomain + Domain = $this.Domain + RequiredRole = $this.RequiredRole + Role = $this.Role + } + } + + [bool] Test() + { + $currentState = $this.Get() + return ($currentState.Domain -eq $currentState.RequiredDomain) -and (($null -eq $currentState.RequiredRole) -or ($currentState.Role -eq $currentState.RequiredRole)) + } + + [void] Set() + { + # This resource is only for asserting the Computer Domain requirement. + } +} + +[DSCResource()] +class PowerShellVersion +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $MinVersion + + [DscProperty(NotConfigurable)] + [string] $PowerShellVersion + + [PowerShellVersion] Get() + { + $parsedVersion = $null + if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) + { + throw "'$($this.MinVersion)' is not a valid Version string." + } + + $this.PowerShellVersion = $global:PSVersionTable.PSVersion + + return @{ + MinVersion = $this.MinVersion + PowerShellVersion = $this.PowerShellVersion + } + } + + [bool] Test() + { + $currentState = $this.Get() + return [System.Version]$currentState.PowerShellVersion -ge [System.Version]$currentState.MinVersion + } + + [void] Set() + { + # This resource is only for asserting the PowerShell version requirement. + } +} + +[DSCResource()] +class PnPDevice +{ + # Key required. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty(Mandatory)] + [string] $FriendlyName + + [DscProperty()] + [string] $DeviceClass + + [DscProperty()] + [PnPDeviceState] $Status + + [PnPDevice] Get() + { + $params = @{FriendlyName = $this.FriendlyName } + $params += $this.DeviceClass ? @{Class = $this.DeviceClass } : @{} + $params += $this.Status ? @{Status = $this.Status } : @{} + + $pnpDevice = Get-PnpDevice @params + + return @{ + FriendlyName = $pnpDevice ? $pnpDevice.FriendlyName : $null + DeviceClass = $pnpDevice ? $pnpDevice.Class : $null + Status = $pnpDevice ? $pnpDevice.Status : [PnPDeviceState]::UNKNOWN + } + } + + [bool] Test() + { + $currentState = $this.Get() + # If the device wasn't found with the specified parameters, the FriendlyName in the current state will be null + return ($this.FriendlyName -eq $currentState.FriendlyName) + } + + [void] Set() + { + # This resource is only for asserting the PnP Device requirement. + } +} From e5ced65b454f0b97a55efcf5a128ce39f66e65b3 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 1 Nov 2024 23:40:16 -0500 Subject: [PATCH 02/16] Add tests --- .../Microsoft.Windows.Assertion.psm1 | 20 +- .../Microsoft.Windows.Assertion.Tests.ps1 | 426 ++++++++++++++++++ 2 files changed, 438 insertions(+), 8 deletions(-) create mode 100644 tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 83bd5c73..d934a08d 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -30,8 +30,8 @@ class OsEditionId $this.Edition = Get-ComputerInfo | Select-Object -ExpandProperty WindowsEditionId return @{ - RequredEdition = $this.RequiredEdition - Edition = $this.Edition + RequiredEdition = $this.RequiredEdition + Edition = $this.Edition } } @@ -361,7 +361,9 @@ class CsDomain [bool] Test() { $currentState = $this.Get() - return ($currentState.Domain -eq $currentState.RequiredDomain) -and (($null -eq $currentState.RequiredRole) -or ($currentState.Role -eq $currentState.RequiredRole)) + if ($currentState.Domain -ne $currentState.RequiredDomain) { return $false } # If domains don't match + if (!$currentState.RequiredRole) { return $true } # RequiredRole is null and domains match + return ($currentState.RequiredRole -eq $currentState.Role) # Return whether the roles match } [void] Set() @@ -433,12 +435,13 @@ class PnPDevice $params += $this.DeviceClass ? @{Class = $this.DeviceClass } : @{} $params += $this.Status ? @{Status = $this.Status } : @{} - $pnpDevice = Get-PnpDevice @params + $pnpDevice = @(Get-PnpDevice @params) + # It's possible that multiple PNP devices match, but as long as one matches then the assertion succeeds return @{ - FriendlyName = $pnpDevice ? $pnpDevice.FriendlyName : $null - DeviceClass = $pnpDevice ? $pnpDevice.Class : $null - Status = $pnpDevice ? $pnpDevice.Status : [PnPDeviceState]::UNKNOWN + FriendlyName = $pnpDevice ? $pnpDevice[0].FriendlyName : $null + DeviceClass = $pnpDevice ? $pnpDevice[0].Class : $null + Status = $pnpDevice ? $pnpDevice[0].Status : [PnPDeviceState]::UNKNOWN } } @@ -446,7 +449,8 @@ class PnPDevice { $currentState = $this.Get() # If the device wasn't found with the specified parameters, the FriendlyName in the current state will be null - return ($this.FriendlyName -eq $currentState.FriendlyName) + # If a device was found, then FriendlyName will not be null + return (!!$currentState.FriendlyName) } [void] Set() diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 new file mode 100644 index 00000000..0a2a68c8 --- /dev/null +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -0,0 +1,426 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +using module Microsoft.Windows.Assertion + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.Developer PowerShell module. +#> + +BeforeAll { + if ($null -eq (Get-Module -ListAvailable -Name PSDesiredStateConfiguration)) + { + Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck + } + Import-Module Microsoft.Windows.Assertion +} + +Describe 'List available DSC resources' { + It 'Shows DSC Resources' { + $expectedDSCResources = "OsEditionId", "SystemArchitecture", "ProcessorArchitecture", "HyperVisorPresent", "OsInstallDate", "OsVersion", "CsManufacturer", "CsModel", "CsDomain", "PowerShellVersion", "PnPDevice" + $availableDSCResources = (Get-DscResource -Module Microsoft.Windows.Assertion).Name + $availableDSCResources.length | Should -Be 11 + $availableDSCResources | Where-Object { $expectedDSCResources -notcontains $_ } | Should -BeNullOrEmpty -ErrorAction Stop + } +} + +InModuleScope -ModuleName Microsoft.Windows.Assertion { + Describe 'SystemArchitecture' { + BeforeAll { + Mock Get-ComputerInfo { return @{OsArchitecture = 'TestValue' } } + } + + $script:SystemArchitectureResource = [SystemArchitecture]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $SystemArchitectureResource.Get() + $initialState.Architecture | Should -Be 'TestValue' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $SystemArchitectureResource.RequiredArchitecture = 'TestValue' + $SystemArchitectureResource.Test() | Should -Be $true + } + It 'Should not match' { + $SystemArchitectureResource.RequiredArchitecture = 'Value' + $SystemArchitectureResource.Test() | Should -Be $false + } + } + + AfterAll { + + } + } + + Describe 'OsEditionId' { + BeforeAll { + Mock Get-ComputerInfo { return @{WindowsEditionId = 'TestValue' } } + } + + $script:OsEditionResource = [OsEditionId]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $OsEditionResource.Get() + $initialState.Edition | Should -Be 'TestValue' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $OsEditionResource.RequiredEdition = 'TestValue' + $OsEditionResource.Test() | Should -Be $true + } + It 'Should not match' { + $OsEditionResource.RequiredEdition = 'Value' + $OsEditionResource.Test() | Should -Be $false + } + } + } + + Describe 'ProcessorArchitecture' { + BeforeAll { + $script:CurrentArchitecture = $env:PROCESSOR_ARCHITECTURE + $env:PROCESSOR_ARCHITECTURE = 'TestValue' + } + + $script:ProcessorArchitectureResource = [ProcessorArchitecture]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $ProcessorArchitectureResource.Get() + $initialState.Architecture | Should -Be 'TestValue' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $ProcessorArchitectureResource.RequiredArchitecture = 'TestValue' + $ProcessorArchitectureResource.Test() | Should -Be $true + } + It 'Should not match' { + $ProcessorArchitectureResource.RequiredArchitecture = 'Value' + $ProcessorArchitectureResource.Test() | Should -Be $false + } + } + + } + + Describe 'HyperVisorPresent' { + BeforeAll { + Mock Get-ComputerInfo { return @{HyperVisorPresent = $true } } + } + + $script:HyperVisorResource = [HyperVisorPresent]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $HyperVisorResource.Get() + $initialState.HyperVisorPresent | Should -Be $true + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $HyperVisorResource.Required = $true + $HyperVisorResource.Test() | Should -Be $true + } + It 'Should not match' { + $HyperVisorResource.Required = $false + $HyperVisorResource.Test() | Should -Be $false + } + } + } + + Describe 'OsInstallDate' { + BeforeAll { + $script:MockOsInstallDate = 'Saturday, November 2, 2024 12:30:00 AM' + Mock Get-ComputerInfo { return @{OsInstallDate = $script:MockOsInstallDate } } + } + + $script:OsInstallDateResource = [OsInstallDate]::new() + + It 'Default Before to todays date' -Tag 'Get' { + $initialState = $OsInstallDateResource.Get() + $initialState.InstallDate | Should -Be $([System.DateTimeOffset]::Parse($script:MockOsInstallDate)) + ([System.DateTimeOffset]$initialState.Before).Date | Should -Be $(([System.DateTimeOffset]::Now).Date) + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match between dates' { + $OsInstallDateResource.Before = 'Sunday, November 3, 2024 12:00:00 AM' + $OsInstallDateResource.After = 'Friday, November 1, 2024 12:00:00 AM' + $OsInstallDateResource.Test() | Should -Be $true + } + It 'Should fail if before constraint is violated' { + $OsInstallDateResource.Before = 'Friday, November 1, 2024 12:00:00 AM' + $OsInstallDateResource.Test() | Should -Be $false + } + It 'Should fail if after constraint is violated' { + $OsInstallDateResource.After = 'Sunday, November 3, 2024 12:00:00 AM' + $OsInstallDateResource.Test() | Should -Be $false + } + It 'Should take minutes and seconds into consideration' { + $OsInstallDateResource.Before = 'Saturday, November 2, 2024 12:29:59 AM' + $OsInstallDateResource.Test() | Should -Be $false + } + It 'Should throw if before is not a date' { + $OsInstallDateResource.Before = 'This is not a date' + { $OsInstallDateResource.Test() } | Should -Throw + } + It 'Should throw if after is not a date' { + $OsInstallDateResource.Before = 'This is not a date' + { $OsInstallDateResource.Test() } | Should -Throw + } + } + } + + Describe 'OsVersion' { + BeforeAll { + Mock Get-ComputerInfo { return @{OsVersion = '1.2.0' } } + } + + $script:OsVersionResource = [OsVersion]::new() + + It 'Get Current Property' -Tag 'Get' { + $OsVersionResource.MinVersion = '0.0' + $initialState = $OsVersionResource.Get() + $initialState.MinVersion | Should -Be '0.0' + $initialState.OsVersion | Should -Be '1.2.0' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should succeed' { + $OsVersionResource.MinVersion = '1.0.0' + $OsVersionResource.Test() | Should -Be $true + } + It 'Should fail' { + $OsVersionResource.MinVersion = '1.2.1' + $OsVersionResource.Test() | Should -Be $false + } + It 'Should throw if MinVersion is not a version' { + $OsVersionResource.MinVersion = 'This is not a version' + { $OsVersionResource.Test() } | Should -Throw + } + } + } + + Describe 'CsManufacturer' { + BeforeAll { + Mock Get-ComputerInfo { return @{CsManufacturer = 'TestValue' } } + } + + $script:CsManufacturerResource = [CsManufacturer]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsManufacturerResource.Get() + $initialState.Manufacturer | Should -Be 'TestValue' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $CsManufacturerResource.RequiredManufacturer = 'TestValue' + $CsManufacturerResource.Test() | Should -Be $true + } + It 'Should not match' { + $CsManufacturerResource.RequiredManufacturer = 'Value' + $CsManufacturerResource.Test() | Should -Be $false + } + } + } + + Describe 'CsModel' { + BeforeAll { + Mock Get-ComputerInfo { return @{CsModel = 'TestValue' } } + } + + $script:CsModelResource = [CsModel]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsModelResource.Get() + $initialState.Model | Should -Be 'TestValue' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $CsModelResource.RequiredModel = 'TestValue' + $CsModelResource.Test() | Should -Be $true + } + It 'Should not match' { + $CsModelResource.RequiredModel = 'Value' + $CsModelResource.Test() | Should -Be $false + } + } + } + + Describe 'CsDomain' { + BeforeAll { + Mock Get-ComputerInfo { return @{CsDomain = 'TestDomain'; CsDomainRole = 'TestRole' } } + } + + $script:CsDomainResource = [CsDomain]::new() + + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsDomainResource.Get() + $initialState.Domain | Should -Be 'TestDomain' + $initialState.Role | Should -Be 'TestRole' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Domain is specified and role is null should match' { + $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.Test() | Should -Be $true + } + It 'Domain is specified and role is null should not match' { + $CsDomainResource.RequiredDomain = 'Domain' + $CsDomainResource.Test() | Should -Be $false + } + It 'Domain and role specified should match' { + $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.RequiredRole = 'TestRole' + $CsDomainResource.Test() | Should -Be $true + } + It 'Domain and role specified should not match' { + $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.RequiredRole = 'Role' + $CsDomainResource.Test() | Should -Be $false + } + } + } + + Describe 'PowerShellVersion' { + BeforeAll { + $global:OriginalPsVersion = $global:PSVersionTable.PSVersion + $global:PSVersionTable.PSVersion = [System.Version]'7.2.0.0' + } + + $script:PowerShellVersionResource = [PowerShellVersion]::new() + + It 'Get Current Property' -Tag 'Get' { + $PowerShellVersionResource.MinVersion = '0.0' + $initialState = $PowerShellVersionResource.Get() + $initialState.MinVersion | Should -Be '0.0' + $initialState.PowerShellVersion | Should -Be '7.2.0.0' + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should succeed' { + $PowerShellVersionResource.MinVersion = '7.2' + $PowerShellVersionResource.Test() | Should -Be $true + } + It 'Should fail' { + $PowerShellVersionResource.MinVersion = '7.2.1' + $PowerShellVersionResource.Test() | Should -Be $false + } + It 'Should throw if MinVersion is not a version' { + $PowerShellVersionResource.MinVersion = 'This is not a version' + { $PowerShellVersionResource.Test() } | Should -Throw + } + } + + AfterAll { + $global:PSVersionTable.PSVersion = $global:OriginalPsVersion + } + } + + + Describe 'PnPDevice' { + BeforeAll { + + $script:TestPnPDevice = @{ + FriendlyName = 'TestName' + Class = 'TestClass' + Status = 'OK' + } + + # Mock when all parameters are present + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and $DeviceClass -eq "TestClass" -and $Status -eq "OK" } -MockWith { return $script:TestPnPDevice } + # Mock when two parameters are present + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and $DeviceClass -eq "TestClass" -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } + # Mock when one parameter is present + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and [String]::IsNullOrWhiteSpace($DeviceClass) -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } + # Catch-all Mock + Mock Get-PnPDevice -ParameterFilter { } -MockWith { return @{ FriendlyName = $null; Class = $null; Status = 'UNKNOWN' } } + } + + BeforeEach { + # Because of the way the Status enum works, the instance of the resource needs to be re-created for each test + $script:PnPDeviceResource = [PnPDevice]::new() + } + + $script:PnPDeviceResource = [PnPDevice]::new() + Context 'Get Current Property' -Tag 'Get' { + It 'Should match a device with one property specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' + $initialState.DeviceClass | Should -Be 'TestClass' + $initialState.Status | Should -Be 'OK' + } + It 'Should match a device with two properties specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' + $initialState.DeviceClass | Should -Be 'TestClass' + $initialState.Status | Should -Be 'OK' + } + It 'Should match a device with all properties specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Status = 'OK' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' + $initialState.DeviceClass | Should -Be 'TestClass' + $initialState.Status | Should -Be 'OK' + } + It 'Should not match a device with bad FriendlyName' { + $PnPDeviceResource.FriendlyName = 'Name' + $initialState = $PnPDeviceResource.Get() + !$initialState.FriendlyName | Should -Be $true + !$initialState.DeviceClass | Should -Be $true + $initialState.Status | Should -Be 'UNKNOWN' + } + It 'Should not match a device with bad status' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Status = 'ERROR' + $initialState = $PnPDeviceResource.Get() + !$initialState.FriendlyName | Should -Be $true + !$initialState.DeviceClass | Should -Be $true + $initialState.Status | Should -Be 'UNKNOWN' + } + } + + Context 'Test Current Property' -Tag 'Test' { + It 'Should match a device with one property specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.Test() | Should -Be $true + } + It 'Should match a device with two properties specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Test() | Should -Be $true + } + It 'Should match a device with all properties specified' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Status = 'OK' + $PnPDeviceResource.Test() | Should -Be $true + } + It 'Should not match a device with bad FriendlyName' { + $PnPDeviceResource.FriendlyName = 'Name' + $PnPDeviceResource.Status = 'OK' + $PnPDeviceResource.Test() | Should -Be $false + } + It 'Should not match a device with bad status' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Status = 'ERROR' + $PnPDeviceResource.Test() | Should -Be $false + } + } + } +} + + +AfterAll { +} \ No newline at end of file From dce219b3fbd237656cb779c1ab2524a50e7dcdf9 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 2 Nov 2024 14:55:53 -0500 Subject: [PATCH 03/16] Add documentation for PnPDevice --- .../Microsoft.Windows.Assertion/PnPDevice.md | 71 +++++++++++++++++++ .../Microsoft.Windows.Assertion.psm1 | 15 ++-- 2 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 resources/Help/Microsoft.Windows.Assertion/PnPDevice.md diff --git a/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md new file mode 100644 index 00000000..b42ec041 --- /dev/null +++ b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md @@ -0,0 +1,71 @@ +--- +external help file: Microsoft.Windows.Assertion.psm1-Help.xml +Module Name: Microsoft.Windows.Assertion +ms.date: 11/2/2024 +online version: +schema: 2.0.0 +title: PnPDevice +--- + +# PnPDevice + +## SYNOPSIS + +Ensures at least one PnP Device is connected which matches the required parameters + +## DESCRIPTION + +The `PnPDevice` DSC Resource allows you to check for specific PnP Devices on the system as a pre-requisite for invoking other DSC Resources. This resource does not have the capability to modify PnP Device information. + +## PARAMETERS + +**Parameter**|**Attribute**|**DataType**|**Description**|**Allowed Values** +:-----|:-----|:-----|:-----|:----- +`FriendlyName`|Optional|String[]|The name of the PnP Device to be found| +`DeviceClass`|Optional|String[]|The PnP Class of the PnP Device to be found.| For exampe: `Display` or `Keyboard` or `PrintQueue` +`Status`|Optional|String]]|The current status of the PnP Device to be found|`OK`, `ERROR`, `DEGRADED`, `UNKNOWN` + +## EXAMPLES + +### Example 1 + +```powershell +# Check that a device with a specific name is connected +$params = @{ + FriendlyName = 'NVIDIA RTX A1000 Laptop GPU' +} +Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Microsoft.Windows.Assertion +``` + +### EXAMPLE 2 + +```powershell +# Check that any PnP Device is in the error state +$params = @{ + Status = 'ERROR' +} +Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Microsoft.Windows.Assertion +``` + +### EXAMPLE 3 + +```powershell +# Check that any Keyboard or Mouse is in the error state +$params = @{ + DeviceClass = @('Keyboard'; 'Mouse') + Status = 'ERROR' +} +Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Microsoft.Windows.Assertion +``` + +### EXAMPLE 4 + +```powershell +# Check that a specific device is operational +$params = @{ + FrienlyName = 'Follow-You-Printing' + DeviceClass = 'PrintQueue' + Status = 'OK' +} +Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Microsoft.Windows.Assertion +``` \ No newline at end of file diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index d934a08d..fedb47bb 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -421,17 +421,18 @@ class PnPDevice [string]$SID [DscProperty(Mandatory)] - [string] $FriendlyName + [string[]] $FriendlyName [DscProperty()] - [string] $DeviceClass + [string[]] $DeviceClass [DscProperty()] - [PnPDeviceState] $Status + [PnPDeviceState[]] $Status [PnPDevice] Get() { - $params = @{FriendlyName = $this.FriendlyName } + $params = @{} + $params += $this.FriendlyName ? @{FriendlyName = $this.FriendlyName } : @{} $params += $this.DeviceClass ? @{Class = $this.DeviceClass } : @{} $params += $this.Status ? @{Status = $this.Status } : @{} @@ -439,9 +440,9 @@ class PnPDevice # It's possible that multiple PNP devices match, but as long as one matches then the assertion succeeds return @{ - FriendlyName = $pnpDevice ? $pnpDevice[0].FriendlyName : $null - DeviceClass = $pnpDevice ? $pnpDevice[0].Class : $null - Status = $pnpDevice ? $pnpDevice[0].Status : [PnPDeviceState]::UNKNOWN + FriendlyName = $pnpDevice ? $pnpDevice.FriendlyName : $null + DeviceClass = $pnpDevice ? $pnpDevice.Class : $null + Status = $pnpDevice ? $pnpDevice.Status : [PnPDeviceState]::UNKNOWN } } From e2e52575e634d37ce5989e1c62f396f02f6cfff9 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 2 Nov 2024 15:00:07 -0500 Subject: [PATCH 04/16] Update tags --- .../Microsoft.Windows.Assertion.psd1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 index 007bbd06..c875ba3f 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 @@ -26,10 +26,17 @@ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. Tags = @( - 'PSDscResource_EditionID', + 'PSDscResource_OsEditionId', 'PSDscResource_SystemArchitecture', + 'PSDscResource_ProcessorArchitecture', 'PSDscResource_HyperVisorPresent', - 'PSDscResource_OsInstallDate' + 'PSDscResource_OsInstallDate', + 'PSDscResource_OsVersion', + 'PSDscResource_CsManufacturer', + 'PSDscResource_CsModel', + 'PSDscResource_CsDomain', + 'PSDscResource_PowerShellVersion', + 'PSDscResource_PnPDevice' ) # Prerelease string of this module From a53c35fa04f924c2f49e74945d1db3c19e128350 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 2 Nov 2024 15:04:14 -0500 Subject: [PATCH 05/16] Comment Cleanup and lint --- .../Microsoft.Windows.Assertion.psd1 | 6 +++--- .../Microsoft.Windows.Assertion.psm1 | 10 +++++----- .../Microsoft.Windows.Assertion.Tests.ps1 | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 index c875ba3f..b8a67dbc 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 @@ -10,7 +10,7 @@ Description = 'DSC Module for ensuring the system meets certain specifications' PowerShellVersion = '7.2' DscResourcesToExport = @( - 'OsEditionId', + 'OsEditionId', 'SystemArchitecture', 'ProcessorArchitecture', 'HyperVisorPresent', @@ -26,7 +26,7 @@ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. Tags = @( - 'PSDscResource_OsEditionId', + 'PSDscResource_OsEditionId', 'PSDscResource_SystemArchitecture', 'PSDscResource_ProcessorArchitecture', 'PSDscResource_HyperVisorPresent', @@ -38,7 +38,7 @@ 'PSDscResource_PowerShellVersion', 'PSDscResource_PnPDevice' ) - + # Prerelease string of this module Prerelease = 'alpha' } diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index fedb47bb..1ad63065 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -113,7 +113,7 @@ class ProcessorArchitecture [void] Set() { - # This resource is only for asserting the System Architecture requirement. + # This resource is only for asserting the Processor Architecture requirement. } } @@ -160,7 +160,7 @@ class OsInstallDate [string]$SID [DscProperty()] - [string] $Before = [System.DateTime]::Now + [string] $Before = [System.DateTime]::Now [DscProperty()] [string] $After @@ -321,7 +321,7 @@ class CsModel [void] Set() { - # This resource is only for asserting the Computer Manufacturer requirement. + # This resource is only for asserting the Computer Model requirement. } } @@ -334,7 +334,7 @@ class CsDomain [DscProperty(Mandatory)] [string] $RequiredDomain - + [DscProperty()] [string] $RequiredRole @@ -422,7 +422,7 @@ class PnPDevice [DscProperty(Mandatory)] [string[]] $FriendlyName - + [DscProperty()] [string[]] $DeviceClass diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index 0a2a68c8..9d12c72c 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -423,4 +423,4 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { AfterAll { -} \ No newline at end of file +} From 7786752282d064f0dd78ea64c59e844094cac956 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 4 Nov 2024 14:21:29 -0600 Subject: [PATCH 06/16] Fix ScriptAnalyser issues --- .editorconfig | 16 ++ .../Microsoft.Windows.Assertion.Tests.ps1 | 224 +++++++++--------- 2 files changed, 128 insertions(+), 112 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..509641a4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# top-most EditorConfig file +root=true + +# Apply Windows-style newlines with a newline ending on every file, using UTF-8, and removing extra whitespace before newlines +[*] +end_of_line = crlf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true + +# Overrides for Markdown Files - Use tab for indents (accessibility) +[*.md] +indent_style = tab + +[{allow.txt,excludes.txt,patterns.txt}] +end_of_line = lf diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index 9d12c72c..476e9aff 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -34,12 +34,12 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } $script:SystemArchitectureResource = [SystemArchitecture]::new() - + It 'Get Current Property' -Tag 'Get' { $initialState = $SystemArchitectureResource.Get() $initialState.Architecture | Should -Be 'TestValue' } - + Context 'Test Current Property' -Tag 'Test' { It 'Should match' { $SystemArchitectureResource.RequiredArchitecture = 'TestValue' @@ -52,7 +52,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } AfterAll { - + } } @@ -63,18 +63,18 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsEditionResource = [OsEditionId]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $OsEditionResource.Get() - $initialState.Edition | Should -Be 'TestValue' + It 'Get Current Property' -Tag 'Get' { + $initialState = $OsEditionResource.Get() + $initialState.Edition | Should -Be 'TestValue' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match' { - $OsEditionResource.RequiredEdition = 'TestValue' + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $OsEditionResource.RequiredEdition = 'TestValue' $OsEditionResource.Test() | Should -Be $true } - It 'Should not match' { - $OsEditionResource.RequiredEdition = 'Value' + It 'Should not match' { + $OsEditionResource.RequiredEdition = 'Value' $OsEditionResource.Test() | Should -Be $false } } @@ -88,18 +88,18 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:ProcessorArchitectureResource = [ProcessorArchitecture]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $ProcessorArchitectureResource.Get() - $initialState.Architecture | Should -Be 'TestValue' + It 'Get Current Property' -Tag 'Get' { + $initialState = $ProcessorArchitectureResource.Get() + $initialState.Architecture | Should -Be 'TestValue' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match' { - $ProcessorArchitectureResource.RequiredArchitecture = 'TestValue' + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $ProcessorArchitectureResource.RequiredArchitecture = 'TestValue' $ProcessorArchitectureResource.Test() | Should -Be $true } - It 'Should not match' { - $ProcessorArchitectureResource.RequiredArchitecture = 'Value' + It 'Should not match' { + $ProcessorArchitectureResource.RequiredArchitecture = 'Value' $ProcessorArchitectureResource.Test() | Should -Be $false } } @@ -113,18 +113,18 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:HyperVisorResource = [HyperVisorPresent]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $HyperVisorResource.Get() + It 'Get Current Property' -Tag 'Get' { + $initialState = $HyperVisorResource.Get() $initialState.HyperVisorPresent | Should -Be $true } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match' { - $HyperVisorResource.Required = $true + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $HyperVisorResource.Required = $true $HyperVisorResource.Test() | Should -Be $true } - It 'Should not match' { - $HyperVisorResource.Required = $false + It 'Should not match' { + $HyperVisorResource.Required = $false $HyperVisorResource.Test() | Should -Be $false } } @@ -138,35 +138,35 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsInstallDateResource = [OsInstallDate]::new() - It 'Default Before to todays date' -Tag 'Get' { - $initialState = $OsInstallDateResource.Get() + It 'Default Before to todays date' -Tag 'Get' { + $initialState = $OsInstallDateResource.Get() $initialState.InstallDate | Should -Be $([System.DateTimeOffset]::Parse($script:MockOsInstallDate)) ([System.DateTimeOffset]$initialState.Before).Date | Should -Be $(([System.DateTimeOffset]::Now).Date) } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match between dates' { - $OsInstallDateResource.Before = 'Sunday, November 3, 2024 12:00:00 AM' - $OsInstallDateResource.After = 'Friday, November 1, 2024 12:00:00 AM' + Context 'Test Current Property' -Tag 'Test' { + It 'Should match between dates' { + $OsInstallDateResource.Before = 'Sunday, November 3, 2024 12:00:00 AM' + $OsInstallDateResource.After = 'Friday, November 1, 2024 12:00:00 AM' $OsInstallDateResource.Test() | Should -Be $true } - It 'Should fail if before constraint is violated' { - $OsInstallDateResource.Before = 'Friday, November 1, 2024 12:00:00 AM' + It 'Should fail if before constraint is violated' { + $OsInstallDateResource.Before = 'Friday, November 1, 2024 12:00:00 AM' $OsInstallDateResource.Test() | Should -Be $false } - It 'Should fail if after constraint is violated' { - $OsInstallDateResource.After = 'Sunday, November 3, 2024 12:00:00 AM' + It 'Should fail if after constraint is violated' { + $OsInstallDateResource.After = 'Sunday, November 3, 2024 12:00:00 AM' $OsInstallDateResource.Test() | Should -Be $false } - It 'Should take minutes and seconds into consideration' { + It 'Should take minutes and seconds into consideration' { $OsInstallDateResource.Before = 'Saturday, November 2, 2024 12:29:59 AM' $OsInstallDateResource.Test() | Should -Be $false } - It 'Should throw if before is not a date' { + It 'Should throw if before is not a date' { $OsInstallDateResource.Before = 'This is not a date' { $OsInstallDateResource.Test() } | Should -Throw } - It 'Should throw if after is not a date' { + It 'Should throw if after is not a date' { $OsInstallDateResource.Before = 'This is not a date' { $OsInstallDateResource.Test() } | Should -Throw } @@ -180,23 +180,23 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsVersionResource = [OsVersion]::new() - It 'Get Current Property' -Tag 'Get' { + It 'Get Current Property' -Tag 'Get' { $OsVersionResource.MinVersion = '0.0' - $initialState = $OsVersionResource.Get() - $initialState.MinVersion | Should -Be '0.0' - $initialState.OsVersion | Should -Be '1.2.0' + $initialState = $OsVersionResource.Get() + $initialState.MinVersion | Should -Be '0.0' + $initialState.OsVersion | Should -Be '1.2.0' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should succeed' { - $OsVersionResource.MinVersion = '1.0.0' + Context 'Test Current Property' -Tag 'Test' { + It 'Should succeed' { + $OsVersionResource.MinVersion = '1.0.0' $OsVersionResource.Test() | Should -Be $true } - It 'Should fail' { - $OsVersionResource.MinVersion = '1.2.1' + It 'Should fail' { + $OsVersionResource.MinVersion = '1.2.1' $OsVersionResource.Test() | Should -Be $false } - It 'Should throw if MinVersion is not a version' { + It 'Should throw if MinVersion is not a version' { $OsVersionResource.MinVersion = 'This is not a version' { $OsVersionResource.Test() } | Should -Throw } @@ -210,18 +210,18 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:CsManufacturerResource = [CsManufacturer]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $CsManufacturerResource.Get() - $initialState.Manufacturer | Should -Be 'TestValue' + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsManufacturerResource.Get() + $initialState.Manufacturer | Should -Be 'TestValue' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match' { - $CsManufacturerResource.RequiredManufacturer = 'TestValue' + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $CsManufacturerResource.RequiredManufacturer = 'TestValue' $CsManufacturerResource.Test() | Should -Be $true } - It 'Should not match' { - $CsManufacturerResource.RequiredManufacturer = 'Value' + It 'Should not match' { + $CsManufacturerResource.RequiredManufacturer = 'Value' $CsManufacturerResource.Test() | Should -Be $false } } @@ -234,18 +234,18 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:CsModelResource = [CsModel]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $CsModelResource.Get() - $initialState.Model | Should -Be 'TestValue' + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsModelResource.Get() + $initialState.Model | Should -Be 'TestValue' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match' { - $CsModelResource.RequiredModel = 'TestValue' + Context 'Test Current Property' -Tag 'Test' { + It 'Should match' { + $CsModelResource.RequiredModel = 'TestValue' $CsModelResource.Test() | Should -Be $true } - It 'Should not match' { - $CsModelResource.RequiredModel = 'Value' + It 'Should not match' { + $CsModelResource.RequiredModel = 'Value' $CsModelResource.Test() | Should -Be $false } } @@ -258,29 +258,29 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:CsDomainResource = [CsDomain]::new() - It 'Get Current Property' -Tag 'Get' { - $initialState = $CsDomainResource.Get() - $initialState.Domain | Should -Be 'TestDomain' - $initialState.Role | Should -Be 'TestRole' + It 'Get Current Property' -Tag 'Get' { + $initialState = $CsDomainResource.Get() + $initialState.Domain | Should -Be 'TestDomain' + $initialState.Role | Should -Be 'TestRole' } - Context 'Test Current Property' -Tag 'Test' { - It 'Domain is specified and role is null should match' { - $CsDomainResource.RequiredDomain = 'TestDomain' + Context 'Test Current Property' -Tag 'Test' { + It 'Domain is specified and role is null should match' { + $CsDomainResource.RequiredDomain = 'TestDomain' $CsDomainResource.Test() | Should -Be $true } - It 'Domain is specified and role is null should not match' { - $CsDomainResource.RequiredDomain = 'Domain' + It 'Domain is specified and role is null should not match' { + $CsDomainResource.RequiredDomain = 'Domain' $CsDomainResource.Test() | Should -Be $false } - It 'Domain and role specified should match' { - $CsDomainResource.RequiredDomain = 'TestDomain' - $CsDomainResource.RequiredRole = 'TestRole' + It 'Domain and role specified should match' { + $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.RequiredRole = 'TestRole' $CsDomainResource.Test() | Should -Be $true } - It 'Domain and role specified should not match' { - $CsDomainResource.RequiredDomain = 'TestDomain' - $CsDomainResource.RequiredRole = 'Role' + It 'Domain and role specified should not match' { + $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.RequiredRole = 'Role' $CsDomainResource.Test() | Should -Be $false } } @@ -288,36 +288,36 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { Describe 'PowerShellVersion' { BeforeAll { - $global:OriginalPsVersion = $global:PSVersionTable.PSVersion + $script:OriginalPsVersion = $global:PSVersionTable.PSVersion $global:PSVersionTable.PSVersion = [System.Version]'7.2.0.0' } $script:PowerShellVersionResource = [PowerShellVersion]::new() - It 'Get Current Property' -Tag 'Get' { + It 'Get Current Property' -Tag 'Get' { $PowerShellVersionResource.MinVersion = '0.0' - $initialState = $PowerShellVersionResource.Get() - $initialState.MinVersion | Should -Be '0.0' - $initialState.PowerShellVersion | Should -Be '7.2.0.0' + $initialState = $PowerShellVersionResource.Get() + $initialState.MinVersion | Should -Be '0.0' + $initialState.PowerShellVersion | Should -Be '7.2.0.0' } - Context 'Test Current Property' -Tag 'Test' { - It 'Should succeed' { - $PowerShellVersionResource.MinVersion = '7.2' + Context 'Test Current Property' -Tag 'Test' { + It 'Should succeed' { + $PowerShellVersionResource.MinVersion = '7.2' $PowerShellVersionResource.Test() | Should -Be $true } It 'Should fail' { - $PowerShellVersionResource.MinVersion = '7.2.1' + $PowerShellVersionResource.MinVersion = '7.2.1' $PowerShellVersionResource.Test() | Should -Be $false } - It 'Should throw if MinVersion is not a version' { + It 'Should throw if MinVersion is not a version' { $PowerShellVersionResource.MinVersion = 'This is not a version' { $PowerShellVersionResource.Test() } | Should -Throw } } AfterAll { - $global:PSVersionTable.PSVersion = $global:OriginalPsVersion + $global:PSVersionTable.PSVersion = $script:OriginalPsVersion } } @@ -338,7 +338,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { # Mock when one parameter is present Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and [String]::IsNullOrWhiteSpace($DeviceClass) -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } # Catch-all Mock - Mock Get-PnPDevice -ParameterFilter { } -MockWith { return @{ FriendlyName = $null; Class = $null; Status = 'UNKNOWN' } } + Mock Get-PnPDevice -ParameterFilter { } -MockWith { return @{ FriendlyName = $null; Class = $null; Status = 'UNKNOWN' } } } BeforeEach { @@ -347,71 +347,71 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } $script:PnPDeviceResource = [PnPDevice]::new() - Context 'Get Current Property' -Tag 'Get' { - It 'Should match a device with one property specified' { + Context 'Get Current Property' -Tag 'Get' { + It 'Should match a device with one property specified' { $PnPDeviceResource.FriendlyName = 'TestName' - $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' $initialState.DeviceClass | Should -Be 'TestClass' $initialState.Status | Should -Be 'OK' } - It 'Should match a device with two properties specified' { + It 'Should match a device with two properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' - $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' $initialState.DeviceClass | Should -Be 'TestClass' $initialState.Status | Should -Be 'OK' } - It 'Should match a device with all properties specified' { + It 'Should match a device with all properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'OK' - $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' + $initialState = $PnPDeviceResource.Get() + $initialState.FriendlyName | Should -Be 'TestName' $initialState.DeviceClass | Should -Be 'TestClass' $initialState.Status | Should -Be 'OK' } - It 'Should not match a device with bad FriendlyName' { + It 'Should not match a device with bad FriendlyName' { $PnPDeviceResource.FriendlyName = 'Name' - $initialState = $PnPDeviceResource.Get() + $initialState = $PnPDeviceResource.Get() !$initialState.FriendlyName | Should -Be $true !$initialState.DeviceClass | Should -Be $true $initialState.Status | Should -Be 'UNKNOWN' } - It 'Should not match a device with bad status' { + It 'Should not match a device with bad status' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'ERROR' - $initialState = $PnPDeviceResource.Get() + $initialState = $PnPDeviceResource.Get() !$initialState.FriendlyName | Should -Be $true !$initialState.DeviceClass | Should -Be $true $initialState.Status | Should -Be 'UNKNOWN' } } - Context 'Test Current Property' -Tag 'Test' { - It 'Should match a device with one property specified' { + Context 'Test Current Property' -Tag 'Test' { + It 'Should match a device with one property specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.Test() | Should -Be $true } - It 'Should match a device with two properties specified' { + It 'Should match a device with two properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Test() | Should -Be $true } - It 'Should match a device with all properties specified' { + It 'Should match a device with all properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'OK' $PnPDeviceResource.Test() | Should -Be $true } - It 'Should not match a device with bad FriendlyName' { + It 'Should not match a device with bad FriendlyName' { $PnPDeviceResource.FriendlyName = 'Name' $PnPDeviceResource.Status = 'OK' $PnPDeviceResource.Test() | Should -Be $false } - It 'Should not match a device with bad status' { + It 'Should not match a device with bad status' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'ERROR' From 58e56fcaf4c968f9bd30dfb251d33a42ebdf1441 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 4 Nov 2024 20:23:04 -0600 Subject: [PATCH 07/16] Add License and Project URIs --- .../Microsoft.Windows.Assertion.psd1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 index b8a67dbc..0fc3b7ab 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 @@ -24,6 +24,12 @@ ) PrivateData = @{ PSData = @{ + # A URL to the license for this module. + LicenseUri = 'https://github.com/microsoft/winget-dsc#MIT-1-ov-file' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/winget-dsc' + # Tags applied to this module. These help with module discovery in online galleries. Tags = @( 'PSDscResource_OsEditionId', From 6b132c6d23d84616fb7df51ba8ecb64d8879a450 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 16:26:13 -0600 Subject: [PATCH 08/16] Get rid of SIDs --- .../Microsoft.Windows.Assertion.psm1 | 227 ++++++------------ 1 file changed, 74 insertions(+), 153 deletions(-) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 1ad63065..17eac6f1 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -1,11 +1,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest -enum PnPDeviceState -{ +enum Ensure { + Present + Absent +} + +enum PnPDeviceState { OK ERROR DEGRADED @@ -13,20 +17,15 @@ enum PnPDeviceState } [DSCResource()] -class OsEditionId -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class OsEditionId { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredEdition [DscProperty(NotConfigurable)] [string] $Edition - [OsEditionId] Get() - { + [OsEditionId] Get() { $this.Edition = Get-ComputerInfo | Select-Object -ExpandProperty WindowsEditionId return @{ @@ -35,33 +34,26 @@ class OsEditionId } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return $currentState.Edition -eq $currentState.RequiredEdition } - [void] Set() - { + [void] Set() { # This resource is only for asserting the Edition ID requirement. } } [DSCResource()] -class SystemArchitecture -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class SystemArchitecture { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredArchitecture [DscProperty(NotConfigurable)] [string] $Architecture - [SystemArchitecture] Get() - { + [SystemArchitecture] Get() { $this.Architecture = Get-ComputerInfo | Select-Object -ExpandProperty OsArchitecture return @{ @@ -70,33 +62,26 @@ class SystemArchitecture } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return $currentState.Architecture -eq $currentState.RequiredArchitecture } - [void] Set() - { + [void] Set() { # This resource is only for asserting the System Architecture requirement. } } [DSCResource()] -class ProcessorArchitecture -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class ProcessorArchitecture { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredArchitecture [DscProperty(NotConfigurable)] [string] $Architecture - [ProcessorArchitecture] Get() - { + [ProcessorArchitecture] Get() { $this.Architecture = $env:PROCESSOR_ARCHITECTURE return @{ @@ -105,61 +90,48 @@ class ProcessorArchitecture } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return $currentState.Architecture -eq $currentState.RequiredArchitecture } - [void] Set() - { + [void] Set() { # This resource is only for asserting the Processor Architecture requirement. } } [DSCResource()] -class HyperVisorPresent -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class HyperVisorPresent { - [DscProperty(Mandatory)] - [bool] $Required + [DscProperty(Key)] + [Ensure] $Ensure [DscProperty(NotConfigurable)] [bool] $HyperVisorPresent - [HyperVisorPresent] Get() - { + [HyperVisorPresent] Get() { $this.HyperVisorPresent = Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent return @{ - Required = $this.Required + Ensure = $this.Ensure HyperVisorPresent = $this.HyperVisorPresent } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() - return $currentState.Required -eq $currentState.HyperVisorPresent + return $currentState.Ensure -eq $currentState.HyperVisorPresent } - [void] Set() - { + [void] Set() { # This resource is only for asserting the presence of a HyperVisor. } } [DSCResource()] -class OsInstallDate -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class OsInstallDate { - [DscProperty()] + [DscProperty(Key)] [string] $Before = [System.DateTime]::Now [DscProperty()] @@ -168,25 +140,18 @@ class OsInstallDate [DscProperty(NotConfigurable)] [string] $InstallDate - [OsInstallDate] Get() - { + [OsInstallDate] Get() { # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.Before, [ref]$parsedBefore)` is erroring - try - { + try { $this.Before = $this.Before ? [System.DateTimeOffset]::Parse($this.Before) : $null - } - catch - { + } catch { throw "'$($this.Before)' is not a valid Date string." } # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.After, [ref]$parsedAfter)` is erroring - try - { + try { $this.After = $this.After ? [System.DateTimeOffset]::Parse($this.After) : $null - } - catch - { + } catch { throw "'$($this.After)' is not a valid Date string." } @@ -199,39 +164,31 @@ class OsInstallDate } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() # this.Get() should always return [System.DateTimeOffset] or $null which can be compared directly # $null should always be treated as less than a [System.DateTimeOffset] return ($currentState.InstallDate -gt $currentState.After) -and ($currentState.InstallDate -lt $currentState.Before) } - [void] Set() - { + [void] Set() { # This resource is only for asserting the OS Install Date. } } # This is the same function from Microsoft.Windows.Developer, just included here as it seemed to make sense [DSCResource()] -class OsVersion -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class OsVersion { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $MinVersion [DscProperty(NotConfigurable)] [string] $OsVersion - [OsVersion] Get() - { + [OsVersion] Get() { $parsedVersion = $null - if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) - { + if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) { throw "'$($this.MinVersion)' is not a valid Version string." } @@ -243,33 +200,26 @@ class OsVersion } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return [System.Version]$currentState.OsVersion -ge [System.Version]$currentState.MinVersion } - [void] Set() - { + [void] Set() { # This resource is only for asserting the os version requirement. } } [DSCResource()] -class CsManufacturer -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class CsManufacturer { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredManufacturer [DscProperty(NotConfigurable)] [string] $Manufacturer - [CsManufacturer] Get() - { + [CsManufacturer] Get() { $this.Manufacturer = Get-ComputerInfo | Select-Object -ExpandProperty CsManufacturer return @{ @@ -278,33 +228,26 @@ class CsManufacturer } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return $currentState.Manufacturer -eq $currentState.RequiredManufacturer } - [void] Set() - { + [void] Set() { # This resource is only for asserting the Computer Manufacturer requirement. } } [DSCResource()] -class CsModel -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class CsModel { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredModel [DscProperty(NotConfigurable)] [string] $Model - [CsModel] Get() - { + [CsModel] Get() { $this.Model = Get-ComputerInfo | Select-Object -ExpandProperty CsModel return @{ @@ -313,26 +256,20 @@ class CsModel } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return $currentState.Model -eq $currentState.RequiredModel } - [void] Set() - { + [void] Set() { # This resource is only for asserting the Computer Model requirement. } } [DSCResource()] -class CsDomain -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class CsDomain { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $RequiredDomain [DscProperty()] @@ -344,8 +281,7 @@ class CsDomain [DscProperty(NotConfigurable)] [string] $Role - [CsDomain] Get() - { + [CsDomain] Get() { $domainInfo = Get-ComputerInfo | Select-Object -Property CsDomain, CsDomainRole $this.Domain = $domainInfo.CsDomain $this.Role = $domainInfo.CsDomainRole @@ -358,38 +294,30 @@ class CsDomain } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() if ($currentState.Domain -ne $currentState.RequiredDomain) { return $false } # If domains don't match if (!$currentState.RequiredRole) { return $true } # RequiredRole is null and domains match return ($currentState.RequiredRole -eq $currentState.Role) # Return whether the roles match } - [void] Set() - { + [void] Set() { # This resource is only for asserting the Computer Domain requirement. } } [DSCResource()] -class PowerShellVersion -{ - # Key required. Do not set. - [DscProperty(Key)] - [string]$SID +class PowerShellVersion { - [DscProperty(Mandatory)] + [DscProperty(Key)] [string] $MinVersion [DscProperty(NotConfigurable)] [string] $PowerShellVersion - [PowerShellVersion] Get() - { + [PowerShellVersion] Get() { $parsedVersion = $null - if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) - { + if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) { throw "'$($this.MinVersion)' is not a valid Version string." } @@ -401,24 +329,21 @@ class PowerShellVersion } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() return [System.Version]$currentState.PowerShellVersion -ge [System.Version]$currentState.MinVersion } - [void] Set() - { + [void] Set() { # This resource is only for asserting the PowerShell version requirement. } } [DSCResource()] -class PnPDevice -{ - # Key required. Do not set. +class PnPDevice { + [DscProperty(Key)] - [string]$SID + [Ensure] $Ensure [DscProperty(Mandatory)] [string[]] $FriendlyName @@ -429,8 +354,7 @@ class PnPDevice [DscProperty()] [PnPDeviceState[]] $Status - [PnPDevice] Get() - { + [PnPDevice] Get() { $params = @{} $params += $this.FriendlyName ? @{FriendlyName = $this.FriendlyName } : @{} $params += $this.DeviceClass ? @{Class = $this.DeviceClass } : @{} @@ -440,22 +364,19 @@ class PnPDevice # It's possible that multiple PNP devices match, but as long as one matches then the assertion succeeds return @{ + Ensure = $pnpDevice ? [Ensure]::Present : [Ensure]::Absent FriendlyName = $pnpDevice ? $pnpDevice.FriendlyName : $null DeviceClass = $pnpDevice ? $pnpDevice.Class : $null Status = $pnpDevice ? $pnpDevice.Status : [PnPDeviceState]::UNKNOWN } } - [bool] Test() - { + [bool] Test() { $currentState = $this.Get() - # If the device wasn't found with the specified parameters, the FriendlyName in the current state will be null - # If a device was found, then FriendlyName will not be null - return (!!$currentState.FriendlyName) + return ($currentState.Ensure -eq $this.Ensure) } - [void] Set() - { + [void] Set() { # This resource is only for asserting the PnP Device requirement. } } From 69549a54ab5c1940e422d44ef074abf919cdf969 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 16:46:34 -0600 Subject: [PATCH 09/16] Fix spelling, Fix tests, Use Ensure when needed --- .github/actions/spelling/allow.txt | 1 + .../actions/spelling/expect/generic_terms.txt | 2 + .../Microsoft.Windows.Assertion/PnPDevice.md | 6 +-- .../Microsoft.Windows.Assertion.psd1 | 4 +- .../Microsoft.Windows.Assertion.psm1 | 20 +++------ .../Microsoft.Windows.Assertion.Tests.ps1 | 43 +++++++------------ 6 files changed, 31 insertions(+), 45 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index b5196fda..3e9c4a87 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -37,3 +37,4 @@ uilt Windo ELSPROBLEMS requ +PDevice diff --git a/.github/actions/spelling/expect/generic_terms.txt b/.github/actions/spelling/expect/generic_terms.txt index 5a88fd33..567e6235 100644 --- a/.github/actions/spelling/expect/generic_terms.txt +++ b/.github/actions/spelling/expect/generic_terms.txt @@ -12,3 +12,5 @@ worktree sortby msft automerge +pnp +RTX diff --git a/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md index b42ec041..45e4ccec 100644 --- a/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md +++ b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md @@ -22,7 +22,7 @@ The `PnPDevice` DSC Resource allows you to check for specific PnP Devices on the **Parameter**|**Attribute**|**DataType**|**Description**|**Allowed Values** :-----|:-----|:-----|:-----|:----- `FriendlyName`|Optional|String[]|The name of the PnP Device to be found| -`DeviceClass`|Optional|String[]|The PnP Class of the PnP Device to be found.| For exampe: `Display` or `Keyboard` or `PrintQueue` +`DeviceClass`|Optional|String[]|The PnP Class of the PnP Device to be found.| For example: `Display` or `Keyboard` or `PrintQueue` `Status`|Optional|String]]|The current status of the PnP Device to be found|`OK`, `ERROR`, `DEGRADED`, `UNKNOWN` ## EXAMPLES @@ -63,9 +63,9 @@ Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Mi ```powershell # Check that a specific device is operational $params = @{ - FrienlyName = 'Follow-You-Printing' + FriendlyName = 'Follow-You-Printing' DeviceClass = 'PrintQueue' Status = 'OK' } Invoke-DscResource -Name PnPDevice -Method Test -Property $params -ModuleName Microsoft.Windows.Assertion -``` \ No newline at end of file +``` diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 index 0fc3b7ab..f82b3053 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psd1 @@ -13,7 +13,7 @@ 'OsEditionId', 'SystemArchitecture', 'ProcessorArchitecture', - 'HyperVisorPresent', + 'HyperVisor', 'OsInstallDate', 'OsVersion', 'CsManufacturer', @@ -35,7 +35,7 @@ 'PSDscResource_OsEditionId', 'PSDscResource_SystemArchitecture', 'PSDscResource_ProcessorArchitecture', - 'PSDscResource_HyperVisorPresent', + 'PSDscResource_HyperVisor', 'PSDscResource_OsInstallDate', 'PSDscResource_OsVersion', 'PSDscResource_CsManufacturer', diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 17eac6f1..8fc97bd5 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -101,26 +101,20 @@ class ProcessorArchitecture { } [DSCResource()] -class HyperVisorPresent { +class HyperVisor { [DscProperty(Key)] [Ensure] $Ensure - [DscProperty(NotConfigurable)] - [bool] $HyperVisorPresent - - [HyperVisorPresent] Get() { - $this.HyperVisorPresent = Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent - + [HyperVisor] Get() { return @{ - Ensure = $this.Ensure - HyperVisorPresent = $this.HyperVisorPresent + Ensure = (Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent) ? [Ensure]::Present : [Ensure]::Absent } } [bool] Test() { $currentState = $this.Get() - return $currentState.Ensure -eq $currentState.HyperVisorPresent + return $currentState.Ensure -eq $this.Ensure } [void] Set() { @@ -365,9 +359,9 @@ class PnPDevice { # It's possible that multiple PNP devices match, but as long as one matches then the assertion succeeds return @{ Ensure = $pnpDevice ? [Ensure]::Present : [Ensure]::Absent - FriendlyName = $pnpDevice ? $pnpDevice.FriendlyName : $null - DeviceClass = $pnpDevice ? $pnpDevice.Class : $null - Status = $pnpDevice ? $pnpDevice.Status : [PnPDeviceState]::UNKNOWN + FriendlyName = $this.FriendlyName + DeviceClass = $this.DeviceClass + Status = $this.Status } } diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index 476e9aff..e320c195 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. using module Microsoft.Windows.Assertion -$ErrorActionPreference = "Stop" +$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest <# @@ -11,8 +11,7 @@ Set-StrictMode -Version Latest #> BeforeAll { - if ($null -eq (Get-Module -ListAvailable -Name PSDesiredStateConfiguration)) - { + if ($null -eq (Get-Module -ListAvailable -Name PSDesiredStateConfiguration)) { Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck } Import-Module Microsoft.Windows.Assertion @@ -20,7 +19,7 @@ BeforeAll { Describe 'List available DSC resources' { It 'Shows DSC Resources' { - $expectedDSCResources = "OsEditionId", "SystemArchitecture", "ProcessorArchitecture", "HyperVisorPresent", "OsInstallDate", "OsVersion", "CsManufacturer", "CsModel", "CsDomain", "PowerShellVersion", "PnPDevice" + $expectedDSCResources = 'OsEditionId', 'SystemArchitecture', 'ProcessorArchitecture', 'HyperVisor', 'OsInstallDate', 'OsVersion', 'CsManufacturer', 'CsModel', 'CsDomain', 'PowerShellVersion', 'PnPDevice' $availableDSCResources = (Get-DscResource -Module Microsoft.Windows.Assertion).Name $availableDSCResources.length | Should -Be 11 $availableDSCResources | Where-Object { $expectedDSCResources -notcontains $_ } | Should -BeNullOrEmpty -ErrorAction Stop @@ -106,25 +105,25 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } - Describe 'HyperVisorPresent' { + Describe 'HyperVisor' { BeforeAll { Mock Get-ComputerInfo { return @{HyperVisorPresent = $true } } } - $script:HyperVisorResource = [HyperVisorPresent]::new() + $script:HyperVisorResource = [HyperVisor]::new() It 'Get Current Property' -Tag 'Get' { $initialState = $HyperVisorResource.Get() - $initialState.HyperVisorPresent | Should -Be $true + $initialState.Ensure | Should -Be 'Present' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $HyperVisorResource.Required = $true + $HyperVisorResource.Ensure = 'Present' $HyperVisorResource.Test() | Should -Be $true } It 'Should not match' { - $HyperVisorResource.Required = $false + $HyperVisorResource.Ensure = 'Absent' $HyperVisorResource.Test() | Should -Be $false } } @@ -332,11 +331,11 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } # Mock when all parameters are present - Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and $DeviceClass -eq "TestClass" -and $Status -eq "OK" } -MockWith { return $script:TestPnPDevice } + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq 'TestName' -and $DeviceClass -eq 'TestClass' -and $Status -eq 'OK' } -MockWith { return $script:TestPnPDevice } # Mock when two parameters are present - Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and $DeviceClass -eq "TestClass" -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq 'TestName' -and $DeviceClass -eq 'TestClass' -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } # Mock when one parameter is present - Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq "TestName" -and [String]::IsNullOrWhiteSpace($DeviceClass) -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } + Mock Get-PnPDevice -ParameterFilter { $FriendlyName -eq 'TestName' -and [String]::IsNullOrWhiteSpace($DeviceClass) -and [String]::IsNullOrWhiteSpace($Status) } -MockWith { return $script:TestPnPDevice } # Catch-all Mock Mock Get-PnPDevice -ParameterFilter { } -MockWith { return @{ FriendlyName = $null; Class = $null; Status = 'UNKNOWN' } } } @@ -351,42 +350,32 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { It 'Should match a device with one property specified' { $PnPDeviceResource.FriendlyName = 'TestName' $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' - $initialState.DeviceClass | Should -Be 'TestClass' - $initialState.Status | Should -Be 'OK' + $initialState.Ensure | Should -Be 'Present' } It 'Should match a device with two properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' - $initialState.DeviceClass | Should -Be 'TestClass' - $initialState.Status | Should -Be 'OK' + $initialState.Ensure | Should -Be 'Present' } It 'Should match a device with all properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'OK' $initialState = $PnPDeviceResource.Get() - $initialState.FriendlyName | Should -Be 'TestName' - $initialState.DeviceClass | Should -Be 'TestClass' - $initialState.Status | Should -Be 'OK' + $initialState.Ensure | Should -Be 'Present' } It 'Should not match a device with bad FriendlyName' { $PnPDeviceResource.FriendlyName = 'Name' $initialState = $PnPDeviceResource.Get() - !$initialState.FriendlyName | Should -Be $true - !$initialState.DeviceClass | Should -Be $true - $initialState.Status | Should -Be 'UNKNOWN' + $initialState.Ensure | Should -Be 'Absent' } It 'Should not match a device with bad status' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'ERROR' $initialState = $PnPDeviceResource.Get() - !$initialState.FriendlyName | Should -Be $true - !$initialState.DeviceClass | Should -Be $true - $initialState.Status | Should -Be 'UNKNOWN' + $initialState.Ensure | Should -Be 'Absent' } } From f30a48c34eec9b7b3e6b06eb8180e41153b5cfbd Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 18:43:52 -0600 Subject: [PATCH 10/16] Return Correct Types for DSC Functions --- .../Microsoft.Windows.Assertion.psm1 | 186 +++++++----------- .../Microsoft.Windows.Assertion.Tests.ps1 | 69 +++---- 2 files changed, 98 insertions(+), 157 deletions(-) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 8fc97bd5..02c8034f 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -20,23 +20,16 @@ enum PnPDeviceState { class OsEditionId { [DscProperty(Key)] - [string] $RequiredEdition - - [DscProperty(NotConfigurable)] [string] $Edition [OsEditionId] Get() { - $this.Edition = Get-ComputerInfo | Select-Object -ExpandProperty WindowsEditionId - - return @{ - RequiredEdition = $this.RequiredEdition - Edition = $this.Edition - } + $currentState = [OsEditionId]::new() + $currentState.Edition = Get-ComputerInfo | Select-Object -ExpandProperty WindowsEditionId + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Edition -eq $currentState.RequiredEdition + return $this.Get().Edition -eq $this.Edition } [void] Set() { @@ -48,23 +41,16 @@ class OsEditionId { class SystemArchitecture { [DscProperty(Key)] - [string] $RequiredArchitecture - - [DscProperty(NotConfigurable)] [string] $Architecture [SystemArchitecture] Get() { - $this.Architecture = Get-ComputerInfo | Select-Object -ExpandProperty OsArchitecture - - return @{ - RequiredArchitecture = $this.RequiredArchitecture - Architecture = $this.Architecture - } + $currentState = [SystemArchitecture]::new() + $currentState.Architecture = Get-ComputerInfo | Select-Object -ExpandProperty OsArchitecture + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Architecture -eq $currentState.RequiredArchitecture + return $this.Get().Architecture -eq $this.Architecture } [void] Set() { @@ -76,23 +62,16 @@ class SystemArchitecture { class ProcessorArchitecture { [DscProperty(Key)] - [string] $RequiredArchitecture - - [DscProperty(NotConfigurable)] [string] $Architecture [ProcessorArchitecture] Get() { - $this.Architecture = $env:PROCESSOR_ARCHITECTURE - - return @{ - RequiredArchitecture = $this.RequiredArchitecture - Architecture = $this.Architecture - } + $currentState = [ProcessorArchitecture]::new() + $currentState.Architecture = $env:PROCESSOR_ARCHITECTURE + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Architecture -eq $currentState.RequiredArchitecture + return $this.Get().Architecture -eq $this.Architecture } [void] Set() { @@ -107,14 +86,13 @@ class HyperVisor { [Ensure] $Ensure [HyperVisor] Get() { - return @{ - Ensure = (Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent) ? [Ensure]::Present : [Ensure]::Absent - } + $currentState = [HyperVisor]::new() + $currentState.Ensure = (Get-ComputerInfo | Select-Object -ExpandProperty HyperVisorPresent) ? [Ensure]::Present : [Ensure]::Absent + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Ensure -eq $this.Ensure + return $this.Get().Ensure -eq $this.Ensure } [void] Set() { @@ -126,7 +104,7 @@ class HyperVisor { class OsInstallDate { [DscProperty(Key)] - [string] $Before = [System.DateTime]::Now + [string] $Before [DscProperty()] [string] $After @@ -135,34 +113,34 @@ class OsInstallDate { [string] $InstallDate [OsInstallDate] Get() { + $currentState = [OsInstallDate]::new() + # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.Before, [ref]$parsedBefore)` is erroring try { - $this.Before = $this.Before ? [System.DateTimeOffset]::Parse($this.Before) : $null + if ($this.Before) { [System.DateTimeOffset]::Parse($this.Before) } } catch { throw "'$($this.Before)' is not a valid Date string." } # Try-Catch isn't a good way to do this, but `[System.DateTimeOffset]::TryParse($this.After, [ref]$parsedAfter)` is erroring try { - $this.After = $this.After ? [System.DateTimeOffset]::Parse($this.After) : $null + if ($this.After) { [System.DateTimeOffset]::Parse($this.After) } } catch { throw "'$($this.After)' is not a valid Date string." } - $this.InstallDate = [System.DateTimeOffset]::Parse($(Get-ComputerInfo | Select-Object -ExpandProperty OsInstallDate)) - - return @{ - Before = $this.Before - After = $this.After - InstallDate = $this.InstallDate - } + $currentState.Before = $this.Before + $currentState.After = $this.After + $currentState.InstallDate = Get-ComputerInfo | Select-Object -ExpandProperty OsInstallDate + return $currentState } [bool] Test() { $currentState = $this.Get() - # this.Get() should always return [System.DateTimeOffset] or $null which can be compared directly - # $null should always be treated as less than a [System.DateTimeOffset] - return ($currentState.InstallDate -gt $currentState.After) -and ($currentState.InstallDate -lt $currentState.Before) + return ( + [System.DateTimeOffset]$currentState.InstallDate -gt [System.DateTimeOffset]$this.After -and + [System.DateTimeOffset]$currentState.InstallDate -lt [System.DateTimeOffset]$this.Before + ) } [void] Set() { @@ -181,22 +159,19 @@ class OsVersion { [string] $OsVersion [OsVersion] Get() { - $parsedVersion = $null - if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) { + + if ($this.MinVersion -and ![System.Version]::TryParse($this.MinVersion, [ref]$null)) { throw "'$($this.MinVersion)' is not a valid Version string." } - $this.OsVersion = Get-ComputerInfo | Select-Object -ExpandProperty OsVersion - - return @{ - MinVersion = $this.MinVersion - OsVersion = $this.OsVersion - } + $currentState = [OsVersion]::new() + $currentState.MinVersion = $this.MinVersion + $currentState.OsVersion = Get-ComputerInfo | Select-Object -ExpandProperty OsVersion + return $currentState } [bool] Test() { - $currentState = $this.Get() - return [System.Version]$currentState.OsVersion -ge [System.Version]$currentState.MinVersion + return [System.Version]$this.Get().OsVersion -ge [System.Version]$this.MinVersion } [void] Set() { @@ -208,23 +183,16 @@ class OsVersion { class CsManufacturer { [DscProperty(Key)] - [string] $RequiredManufacturer - - [DscProperty(NotConfigurable)] [string] $Manufacturer [CsManufacturer] Get() { - $this.Manufacturer = Get-ComputerInfo | Select-Object -ExpandProperty CsManufacturer - - return @{ - RequiredManufacturer = $this.RequiredManufacturer - Manufacturer = $this.Manufacturer - } + $currentState = [CsManufacturer]::new() + $currentState.Manufacturer = Get-ComputerInfo | Select-Object -ExpandProperty CsManufacturer + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Manufacturer -eq $currentState.RequiredManufacturer + return $this.Get().Manufacturer -eq $this.Manufacturer } [void] Set() { @@ -236,23 +204,16 @@ class CsManufacturer { class CsModel { [DscProperty(Key)] - [string] $RequiredModel - - [DscProperty(NotConfigurable)] [string] $Model [CsModel] Get() { - $this.Model = Get-ComputerInfo | Select-Object -ExpandProperty CsModel - - return @{ - RequiredModel = $this.RequiredModel - Model = $this.Model - } + $currentState = [CsModel]::new() + $currentState.Model = Get-ComputerInfo | Select-Object -ExpandProperty CsModel + return $currentState } [bool] Test() { - $currentState = $this.Get() - return $currentState.Model -eq $currentState.RequiredModel + return $this.Get().Model -eq $this.Model } [void] Set() { @@ -264,35 +225,25 @@ class CsModel { class CsDomain { [DscProperty(Key)] - [string] $RequiredDomain - - [DscProperty()] - [string] $RequiredRole - - [DscProperty(NotConfigurable)] [string] $Domain - [DscProperty(NotConfigurable)] + [DscProperty()] [string] $Role [CsDomain] Get() { $domainInfo = Get-ComputerInfo | Select-Object -Property CsDomain, CsDomainRole - $this.Domain = $domainInfo.CsDomain - $this.Role = $domainInfo.CsDomainRole - - return @{ - RequiredDomain = $this.RequiredDomain - Domain = $this.Domain - RequiredRole = $this.RequiredRole - Role = $this.Role - } + + $currentState = [CsDomain]::new() + $currentState.Domain = $domainInfo.CsDomain + $currentState.Role = $domainInfo.CsDomainRole + return $currentState } [bool] Test() { $currentState = $this.Get() - if ($currentState.Domain -ne $currentState.RequiredDomain) { return $false } # If domains don't match - if (!$currentState.RequiredRole) { return $true } # RequiredRole is null and domains match - return ($currentState.RequiredRole -eq $currentState.Role) # Return whether the roles match + if ($currentState.Domain -ne $this.Domain) { return $false } # If domains don't match + if (!$this.Role) { return $true } # Required Role is null and domains match + return ($currentState.Role -eq $this.Role) # Return whether the roles match } [void] Set() { @@ -310,17 +261,15 @@ class PowerShellVersion { [string] $PowerShellVersion [PowerShellVersion] Get() { - $parsedVersion = $null - if (![System.Version]::TryParse($this.MinVersion, [ref]$parsedVersion)) { + + if ($this.MinVersion -and ![System.Version]::TryParse($this.MinVersion, [ref]$null)) { throw "'$($this.MinVersion)' is not a valid Version string." } - $this.PowerShellVersion = $global:PSVersionTable.PSVersion - - return @{ - MinVersion = $this.MinVersion - PowerShellVersion = $this.PowerShellVersion - } + $currentState = [PowerShellVersion]::new() + $currentState.MinVersion = $this.MinVersion + $currentState.PowerShellVersion = $global:PSVersionTable.PSVersion + return $currentState } [bool] Test() { @@ -339,7 +288,7 @@ class PnPDevice { [DscProperty(Key)] [Ensure] $Ensure - [DscProperty(Mandatory)] + [DscProperty()] [string[]] $FriendlyName [DscProperty()] @@ -357,17 +306,16 @@ class PnPDevice { $pnpDevice = @(Get-PnpDevice @params) # It's possible that multiple PNP devices match, but as long as one matches then the assertion succeeds - return @{ - Ensure = $pnpDevice ? [Ensure]::Present : [Ensure]::Absent - FriendlyName = $this.FriendlyName - DeviceClass = $this.DeviceClass - Status = $this.Status - } + $currentState = [PnPDevice]::new() + $currentState.Ensure = $pnpDevice ? [Ensure]::Present : [Ensure]::Absent + $currentState.FriendlyName = $this.FriendlyName + $currentState.DeviceClass = $this.DeviceClass + $currentState.Status = $this.Status + return $currentState } [bool] Test() { - $currentState = $this.Get() - return ($currentState.Ensure -eq $this.Ensure) + return $this.Get().Ensure -eq $this.Ensure } [void] Set() { diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index e320c195..c27a77e3 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -35,24 +35,19 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:SystemArchitectureResource = [SystemArchitecture]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $SystemArchitectureResource.Get() - $initialState.Architecture | Should -Be 'TestValue' + $SystemArchitectureResource.Get().Architecture | Should -Be 'TestValue' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $SystemArchitectureResource.RequiredArchitecture = 'TestValue' + $SystemArchitectureResource.Architecture = 'TestValue' $SystemArchitectureResource.Test() | Should -Be $true } It 'Should not match' { - $SystemArchitectureResource.RequiredArchitecture = 'Value' + $SystemArchitectureResource.Architecture = 'Value' $SystemArchitectureResource.Test() | Should -Be $false } } - - AfterAll { - - } } Describe 'OsEditionId' { @@ -63,17 +58,16 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsEditionResource = [OsEditionId]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $OsEditionResource.Get() - $initialState.Edition | Should -Be 'TestValue' + $OsEditionResource.Get().Edition | Should -Be 'TestValue' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $OsEditionResource.RequiredEdition = 'TestValue' + $OsEditionResource.Edition = 'TestValue' $OsEditionResource.Test() | Should -Be $true } It 'Should not match' { - $OsEditionResource.RequiredEdition = 'Value' + $OsEditionResource.Edition = 'Value' $OsEditionResource.Test() | Should -Be $false } } @@ -88,21 +82,24 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:ProcessorArchitectureResource = [ProcessorArchitecture]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $ProcessorArchitectureResource.Get() - $initialState.Architecture | Should -Be 'TestValue' + $ProcessorArchitectureResource.Get().Architecture | Should -Be 'TestValue' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $ProcessorArchitectureResource.RequiredArchitecture = 'TestValue' + $ProcessorArchitectureResource.Architecture = 'TestValue' $ProcessorArchitectureResource.Test() | Should -Be $true } It 'Should not match' { - $ProcessorArchitectureResource.RequiredArchitecture = 'Value' + $ProcessorArchitectureResource.Architecture = 'Value' $ProcessorArchitectureResource.Test() | Should -Be $false } } + AfterAll { + $env:PROCESSOR_ARCHITECTURE = $script:CurrentArchitecture + } + } Describe 'HyperVisor' { @@ -113,8 +110,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:HyperVisorResource = [HyperVisor]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $HyperVisorResource.Get() - $initialState.Ensure | Should -Be 'Present' + $HyperVisorResource.Get().Ensure | Should -Be 'Present' } Context 'Test Current Property' -Tag 'Test' { @@ -137,10 +133,11 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsInstallDateResource = [OsInstallDate]::new() - It 'Default Before to todays date' -Tag 'Get' { + It 'Get Current Property' -Tag 'Get' { $initialState = $OsInstallDateResource.Get() + [String]::IsNullOrEmpty($initialState.Before) | Should -Be $true + [String]::IsNullOrEmpty($initialState.After) | Should -Be $true $initialState.InstallDate | Should -Be $([System.DateTimeOffset]::Parse($script:MockOsInstallDate)) - ([System.DateTimeOffset]$initialState.Before).Date | Should -Be $(([System.DateTimeOffset]::Now).Date) } Context 'Test Current Property' -Tag 'Test' { @@ -180,9 +177,8 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:OsVersionResource = [OsVersion]::new() It 'Get Current Property' -Tag 'Get' { - $OsVersionResource.MinVersion = '0.0' $initialState = $OsVersionResource.Get() - $initialState.MinVersion | Should -Be '0.0' + [String]::IsNullOrEmpty($initialState.MinVersion) | Should -Be $true $initialState.OsVersion | Should -Be '1.2.0' } @@ -210,17 +206,16 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:CsManufacturerResource = [CsManufacturer]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $CsManufacturerResource.Get() - $initialState.Manufacturer | Should -Be 'TestValue' + $CsManufacturerResource.Get().Manufacturer | Should -Be 'TestValue' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $CsManufacturerResource.RequiredManufacturer = 'TestValue' + $CsManufacturerResource.Manufacturer = 'TestValue' $CsManufacturerResource.Test() | Should -Be $true } It 'Should not match' { - $CsManufacturerResource.RequiredManufacturer = 'Value' + $CsManufacturerResource.Manufacturer = 'Value' $CsManufacturerResource.Test() | Should -Be $false } } @@ -234,17 +229,16 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:CsModelResource = [CsModel]::new() It 'Get Current Property' -Tag 'Get' { - $initialState = $CsModelResource.Get() - $initialState.Model | Should -Be 'TestValue' + $CsModelResource.Get().Model | Should -Be 'TestValue' } Context 'Test Current Property' -Tag 'Test' { It 'Should match' { - $CsModelResource.RequiredModel = 'TestValue' + $CsModelResource.Model = 'TestValue' $CsModelResource.Test() | Should -Be $true } It 'Should not match' { - $CsModelResource.RequiredModel = 'Value' + $CsModelResource.Model = 'Value' $CsModelResource.Test() | Should -Be $false } } @@ -265,21 +259,21 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { Context 'Test Current Property' -Tag 'Test' { It 'Domain is specified and role is null should match' { - $CsDomainResource.RequiredDomain = 'TestDomain' + $CsDomainResource.Domain = 'TestDomain' $CsDomainResource.Test() | Should -Be $true } It 'Domain is specified and role is null should not match' { - $CsDomainResource.RequiredDomain = 'Domain' + $CsDomainResource.Domain = 'Domain' $CsDomainResource.Test() | Should -Be $false } It 'Domain and role specified should match' { - $CsDomainResource.RequiredDomain = 'TestDomain' - $CsDomainResource.RequiredRole = 'TestRole' + $CsDomainResource.Domain = 'TestDomain' + $CsDomainResource.Role = 'TestRole' $CsDomainResource.Test() | Should -Be $true } It 'Domain and role specified should not match' { - $CsDomainResource.RequiredDomain = 'TestDomain' - $CsDomainResource.RequiredRole = 'Role' + $CsDomainResource.Domain = 'TestDomain' + $CsDomainResource.Role = 'Role' $CsDomainResource.Test() | Should -Be $false } } @@ -294,9 +288,8 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $script:PowerShellVersionResource = [PowerShellVersion]::new() It 'Get Current Property' -Tag 'Get' { - $PowerShellVersionResource.MinVersion = '0.0' $initialState = $PowerShellVersionResource.Get() - $initialState.MinVersion | Should -Be '0.0' + [String]::IsNullOrEmpty($initialState.MinVersion) | Should -Be $true $initialState.PowerShellVersion | Should -Be '7.2.0.0' } From 7803eb3a785b7afbfc866e15e020823084a2886b Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 23:30:55 -0600 Subject: [PATCH 11/16] Add throws to all setter methods; Add related tests --- .configurations/configuration.dsc.yaml | 111 +++++++++++++ .../Microsoft.Windows.Assertion.psm1 | 37 ++++- .../Microsoft.Windows.Assertion.Tests.ps1 | 147 +++++++++++++++++- 3 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 .configurations/configuration.dsc.yaml diff --git a/.configurations/configuration.dsc.yaml b/.configurations/configuration.dsc.yaml new file mode 100644 index 00000000..dcf00561 --- /dev/null +++ b/.configurations/configuration.dsc.yaml @@ -0,0 +1,111 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 + +properties: + resources: + - resource: Microsoft.Windows.Assertion/OsEditionId + id: os-edition + directives: + description: Assert the Os Edition + allowPrerelease: true + settings: + Edition: Enterprise Two + ######################################################################## + # Section: Install Git + ######################################################################## + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: install-git + dependsOn: + - os-edition + directives: + description: Install Git # Although the user probably already has git installed, it's possible that they don't + allowPrerelease: true + settings: + id: Git.Git + source: winget + Ensure: Present + # - resource: Microsoft.WinGet.DSC/WinGetPackage + # dependsOn: + # - install-git + # id: install-github-cli + # directives: + # description: Install GitHub CLI + # allowPrerelease: true + # securityContext: elevated + # settings: + # id: GitHub.cli + # source: winget + # Ensure: Present + # ######################################################################## + # # Section: Configure Git Remotes + # ######################################################################## + # - resource: GitDSC/GitRemote + # id: add-microsoft-upstream + # directives: + # description: Add microsoft/winget-dsc as the upstream remote + # allowPrerelease: true + # settings: + # ProjectDirectory: '${WinGetConfigRoot}\..' + # RemoteName: upstream + # RemoteUrl: https://github.com/microsoft/winget-pkgs.git + # ######################################################################## + # # Section: Install VS-Code + # ######################################################################## + # - resource: Microsoft.WinGet.DSC/WinGetPackage + # id: install-vs-code + # directives: + # description: Install Microsoft Visual Studio Code + # allowPrerelease: true + # settings: + # id: Microsoft.VisualStudioCode + # source: winget + # Ensure: Present + # - resource: Microsoft.VSCode.Dsc/VSCodeExtension + # id: install_vscode-yaml + # dependsOn: + # - install-vs-code + # directives: + # description: Install YAML extension for VSCode + # allowPrerelease: true + # settings: + # Name: redhat.vscode-yaml + # Exist: true + # - resource: Microsoft.VSCode.Dsc/VSCodeExtension + # id: install_editorconfig.editorconfig + # dependsOn: + # - install-vs-code + # directives: + # description: Install EditorConfig extension for VSCode + # allowPrerelease: true + # settings: + # Name: EditorConfig.EditorConfig + # Exist: true + # - resource: Microsoft.VSCode.Dsc/VSCodeExtension + # id: install_ms-vscode.powershell + # dependsOn: + # - install-vs-code + # directives: + # description: Install PowerShell extension for VSCode + # allowPrerelease: true + # settings: + # Name: ms-vscode.powershell + # Exist: true + # ######################################################################## + # # Section: Install PowerShell Modules + # ######################################################################## + # - resource: PowerShellModule/PSModuleResource + # id: install-pester + # directives: + # description: Install Pester module + # allowPrerelease: true + # settings: + # Module_Name: Pester + # Ensure: Present + # - resource: PowerShellModule/PSModuleResource + # id: install-psscriptanalyzer + # directives: + # description: Install PSScriptAnalyzer module + # allowPrerelease: true + # settings: + # Module_Name: PSScriptAnalyzer + # Ensure: Present + configurationVersion: 0.2.0 diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 02c8034f..427d5207 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -34,6 +34,8 @@ class OsEditionId { [void] Set() { # This resource is only for asserting the Edition ID requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Edition)' but received '$($this.Get().Edition)'") } } @@ -55,6 +57,8 @@ class SystemArchitecture { [void] Set() { # This resource is only for asserting the System Architecture requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Architecture)' but received '$($this.Get().Architecture)'") } } @@ -76,6 +80,8 @@ class ProcessorArchitecture { [void] Set() { # This resource is only for asserting the Processor Architecture requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Architecture)' but received '$($this.Get().Architecture)'") } } @@ -97,6 +103,8 @@ class HyperVisor { [void] Set() { # This resource is only for asserting the presence of a HyperVisor. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Ensure)' but received '$($this.Get().Ensure)'") } } @@ -137,14 +145,15 @@ class OsInstallDate { [bool] Test() { $currentState = $this.Get() - return ( - [System.DateTimeOffset]$currentState.InstallDate -gt [System.DateTimeOffset]$this.After -and - [System.DateTimeOffset]$currentState.InstallDate -lt [System.DateTimeOffset]$this.Before - ) + if ($this.Before -and [System.DateTimeOffset]$currentState.InstallDate -gt [System.DateTimeOffset]$this.Before) { return $false } # The IntallDate was later than the specified 'Before' date + if ($this.After -and [System.DateTimeOffset]$currentState.InstallDate -lt [System.DateTimeOffset]$this.After) { return $false } # The InstallDate was earlier than the specified 'After' date + return $true } [void] Set() { # This resource is only for asserting the OS Install Date. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. '$($this.Before)' >= '$($this.Get().InstallDate)' >= '$($this.After)' evaluated to 'False'") } } @@ -176,6 +185,8 @@ class OsVersion { [void] Set() { # This resource is only for asserting the os version requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. '$($this.Get().OsVersion)' >= '$($this.MinVersion)' evaluated to 'False'") } } @@ -197,6 +208,8 @@ class CsManufacturer { [void] Set() { # This resource is only for asserting the Computer Manufacturer requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Manufacturer)' but received '$($this.Get().Manufacturer)'") } } @@ -218,6 +231,8 @@ class CsModel { [void] Set() { # This resource is only for asserting the Computer Model requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Model)' but received '$($this.Get().Model)'") } } @@ -248,6 +263,9 @@ class CsDomain { [void] Set() { # This resource is only for asserting the Computer Domain requirement. + if ($this.Test()) { return } + $currentState = $this.Get() + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. Expected '$($this.Domain)<$($this.Role)>' but received '$($currentState.Domain)<$($currentState.Role)>'") } } @@ -279,6 +297,8 @@ class PowerShellVersion { [void] Set() { # This resource is only for asserting the PowerShell version requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New("Assertion Failed. '$($this.Get().PowerShellVersion)' >= '$($this.MinVersion)' evaluated to 'False'") } } @@ -320,5 +340,14 @@ class PnPDevice { [void] Set() { # This resource is only for asserting the PnP Device requirement. + if ($this.Test()) { return } + throw [System.Configuration.ConfigurationException]::New('Assertion Failed. ' + + $( if ($this.Ensure -eq [Ensure]::Present) { + 'No PnP devices found which matched the parameters' + } else { + 'One or more PnP devices found which matched the parameters' + } + ) + ) } } diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index c27a77e3..b1c727ad 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -48,6 +48,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $SystemArchitectureResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $SystemArchitectureResource.Architecture = 'TestValue' + { $SystemArchitectureResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $SystemArchitectureResource.Architecture = 'Value' + { $SystemArchitectureResource.Set() } | Should -Throw + } + } } Describe 'OsEditionId' { @@ -71,6 +82,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $OsEditionResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $OsEditionResource.Edition = 'TestValue' + { $OsEditionResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $OsEditionResource.Edition = 'Value' + { $OsEditionResource.Set() } | Should -Throw + } + } } Describe 'ProcessorArchitecture' { @@ -96,6 +118,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } } + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $ProcessorArchitectureResource.Architecture = 'TestValue' + { $ProcessorArchitectureResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $ProcessorArchitectureResource.Architecture = 'Value' + { $ProcessorArchitectureResource.Set() } | Should -Throw + } + } + AfterAll { $env:PROCESSOR_ARCHITECTURE = $script:CurrentArchitecture } @@ -123,6 +156,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $HyperVisorResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $HyperVisorResource.Ensure = 'Present' + { $HyperVisorResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $HyperVisorResource.Ensure = 'Absent' + { $HyperVisorResource.Set() } | Should -Throw + } + } } Describe 'OsInstallDate' { @@ -167,6 +211,20 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { { $OsInstallDateResource.Test() } | Should -Throw } } + + Context 'Set Current Property' -Tag 'Set' { + BeforeAll { + $script:OsInstallDateResource = [OsInstallDate]::new() # Reset properties from the -Tag 'Test' methods + } + It 'Should succeed when setting is not required' { + $OsInstallDateResource.Before = 'Sunday, November 3, 2024 12:00:00 AM' + { $OsInstallDateResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $OsInstallDateResource.Before = 'Friday, November 1, 2024 12:00:00 AM' + { $OsInstallDateResource.Set() } | Should -Throw + } + } } Describe 'OsVersion' { @@ -188,7 +246,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $OsVersionResource.Test() | Should -Be $true } It 'Should fail' { - $OsVersionResource.MinVersion = '1.2.1' + $OsVersionResource.MinVersion = '2.0.0' $OsVersionResource.Test() | Should -Be $false } It 'Should throw if MinVersion is not a version' { @@ -196,6 +254,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { { $OsVersionResource.Test() } | Should -Throw } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $OsVersionResource.MinVersion = '1.0.0' + { $OsVersionResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $OsVersionResource.MinVersion = '2.0.0' + { $OsVersionResource.Set() } | Should -Throw + } + } } Describe 'CsManufacturer' { @@ -219,6 +288,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $CsManufacturerResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $CsManufacturerResource.Manufacturer = 'TestValue' + { $CsManufacturerResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $CsManufacturerResource.Manufacturer = 'Value' + { $CsManufacturerResource.Set() } | Should -Throw + } + } } Describe 'CsModel' { @@ -242,6 +322,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $CsModelResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $CsModelResource.Model = 'TestValue' + { $CsModelResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $CsModelResource.Model = 'Value' + { $CsModelResource.Set() } | Should -Throw + } + } } Describe 'CsDomain' { @@ -277,6 +368,20 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { $CsDomainResource.Test() | Should -Be $false } } + + Context 'Set Current Property' -Tag 'Set' { + BeforeAll { + $script:CsDomainResource = [CsDomain]::new() # Reset properties from the -Tag 'Test' methods + } + It 'Should succeed when setting is not required' { + $CsDomainResource.Domain = 'TestDomain' + { $CsDomainResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $CsDomainResource.Domain = 'Domain' + { $CsDomainResource.Set() } | Should -Throw + } + } } Describe 'PowerShellVersion' { @@ -308,6 +413,17 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } } + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $PowerShellVersionResource.MinVersion = '7.2' + { $PowerShellVersionResource.Set() } | Should -Not -Throw + } + It 'Should throw otherwise' { + $PowerShellVersionResource.MinVersion = '7.2.1' + { $PowerShellVersionResource.Set() } | Should -Throw + } + } + AfterAll { $global:PSVersionTable.PSVersion = $script:OriginalPsVersion } @@ -375,30 +491,59 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { Context 'Test Current Property' -Tag 'Test' { It 'Should match a device with one property specified' { $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.Ensure = 'Present' $PnPDeviceResource.Test() | Should -Be $true } It 'Should match a device with two properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Ensure = 'Present' $PnPDeviceResource.Test() | Should -Be $true } It 'Should match a device with all properties specified' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'OK' + $PnPDeviceResource.Ensure = 'Present' $PnPDeviceResource.Test() | Should -Be $true } It 'Should not match a device with bad FriendlyName' { $PnPDeviceResource.FriendlyName = 'Name' $PnPDeviceResource.Status = 'OK' + $PnPDeviceResource.Ensure = 'Present' $PnPDeviceResource.Test() | Should -Be $false } It 'Should not match a device with bad status' { $PnPDeviceResource.FriendlyName = 'TestName' $PnPDeviceResource.DeviceClass = 'TestClass' $PnPDeviceResource.Status = 'ERROR' + $PnPDeviceResource.Ensure = 'Present' $PnPDeviceResource.Test() | Should -Be $false } + It 'Should match a device with bad status being absent' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.DeviceClass = 'TestClass' + $PnPDeviceResource.Status = 'ERROR' + $PnPDeviceResource.Ensure = 'Absent' + $PnPDeviceResource.Test() | Should -Be $true + } + } + + Context 'Set Current Property' -Tag 'Set' { + It 'Should succeed when setting is not required' { + $PnPDeviceResource.FriendlyName = 'TestName' + { $PnPDeviceResource.Set() } | Should -Not -Throw + } + It 'Should throw with One or more PnP devices when ensuring absent' { + $PnPDeviceResource.FriendlyName = 'TestName' + $PnPDeviceResource.Ensure = 'Absent' + { $PnPDeviceResource.Set() } | Should -Throw 'Assertion Failed. One or more PnP devices found which matched the parameters' + } + It 'Should throw with no PnP devices when ensuring present' { + $PnPDeviceResource.FriendlyName = 'Name' + $PnPDeviceResource.Ensure = 'Present' + { $PnPDeviceResource.Set() } | Should -Throw 'Assertion Failed. No PnP devices found which matched the parameters' + } } } } From 29bf69e3959b587ffbf9e06d22867e51876e22b3 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 23:32:50 -0600 Subject: [PATCH 12/16] Spelling --- .../Microsoft.Windows.Assertion.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 index 427d5207..faf2c88b 100644 --- a/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 +++ b/resources/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.psm1 @@ -145,7 +145,7 @@ class OsInstallDate { [bool] Test() { $currentState = $this.Get() - if ($this.Before -and [System.DateTimeOffset]$currentState.InstallDate -gt [System.DateTimeOffset]$this.Before) { return $false } # The IntallDate was later than the specified 'Before' date + if ($this.Before -and [System.DateTimeOffset]$currentState.InstallDate -gt [System.DateTimeOffset]$this.Before) { return $false } # The InstallDate was later than the specified 'Before' date if ($this.After -and [System.DateTimeOffset]$currentState.InstallDate -lt [System.DateTimeOffset]$this.After) { return $false } # The InstallDate was earlier than the specified 'After' date return $true } From a2bd31f6cc0989a193f94d925308f3a79d27da4b Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 23:41:39 -0600 Subject: [PATCH 13/16] I always forget to remove my test files . . . --- .configurations/configuration.dsc.yaml | 111 ------------------------- 1 file changed, 111 deletions(-) delete mode 100644 .configurations/configuration.dsc.yaml diff --git a/.configurations/configuration.dsc.yaml b/.configurations/configuration.dsc.yaml deleted file mode 100644 index dcf00561..00000000 --- a/.configurations/configuration.dsc.yaml +++ /dev/null @@ -1,111 +0,0 @@ -# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 - -properties: - resources: - - resource: Microsoft.Windows.Assertion/OsEditionId - id: os-edition - directives: - description: Assert the Os Edition - allowPrerelease: true - settings: - Edition: Enterprise Two - ######################################################################## - # Section: Install Git - ######################################################################## - - resource: Microsoft.WinGet.DSC/WinGetPackage - id: install-git - dependsOn: - - os-edition - directives: - description: Install Git # Although the user probably already has git installed, it's possible that they don't - allowPrerelease: true - settings: - id: Git.Git - source: winget - Ensure: Present - # - resource: Microsoft.WinGet.DSC/WinGetPackage - # dependsOn: - # - install-git - # id: install-github-cli - # directives: - # description: Install GitHub CLI - # allowPrerelease: true - # securityContext: elevated - # settings: - # id: GitHub.cli - # source: winget - # Ensure: Present - # ######################################################################## - # # Section: Configure Git Remotes - # ######################################################################## - # - resource: GitDSC/GitRemote - # id: add-microsoft-upstream - # directives: - # description: Add microsoft/winget-dsc as the upstream remote - # allowPrerelease: true - # settings: - # ProjectDirectory: '${WinGetConfigRoot}\..' - # RemoteName: upstream - # RemoteUrl: https://github.com/microsoft/winget-pkgs.git - # ######################################################################## - # # Section: Install VS-Code - # ######################################################################## - # - resource: Microsoft.WinGet.DSC/WinGetPackage - # id: install-vs-code - # directives: - # description: Install Microsoft Visual Studio Code - # allowPrerelease: true - # settings: - # id: Microsoft.VisualStudioCode - # source: winget - # Ensure: Present - # - resource: Microsoft.VSCode.Dsc/VSCodeExtension - # id: install_vscode-yaml - # dependsOn: - # - install-vs-code - # directives: - # description: Install YAML extension for VSCode - # allowPrerelease: true - # settings: - # Name: redhat.vscode-yaml - # Exist: true - # - resource: Microsoft.VSCode.Dsc/VSCodeExtension - # id: install_editorconfig.editorconfig - # dependsOn: - # - install-vs-code - # directives: - # description: Install EditorConfig extension for VSCode - # allowPrerelease: true - # settings: - # Name: EditorConfig.EditorConfig - # Exist: true - # - resource: Microsoft.VSCode.Dsc/VSCodeExtension - # id: install_ms-vscode.powershell - # dependsOn: - # - install-vs-code - # directives: - # description: Install PowerShell extension for VSCode - # allowPrerelease: true - # settings: - # Name: ms-vscode.powershell - # Exist: true - # ######################################################################## - # # Section: Install PowerShell Modules - # ######################################################################## - # - resource: PowerShellModule/PSModuleResource - # id: install-pester - # directives: - # description: Install Pester module - # allowPrerelease: true - # settings: - # Module_Name: Pester - # Ensure: Present - # - resource: PowerShellModule/PSModuleResource - # id: install-psscriptanalyzer - # directives: - # description: Install PSScriptAnalyzer module - # allowPrerelease: true - # settings: - # Module_Name: PSScriptAnalyzer - # Ensure: Present - configurationVersion: 0.2.0 From 15a4f84be80bd4db7123f5a0cd278df76b0b166c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 5 Nov 2024 23:49:15 -0600 Subject: [PATCH 14/16] Be more explicit about the error message expected from the setters --- .../Microsoft.Windows.Assertion.Tests.ps1 | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 index b1c727ad..f9a1156e 100644 --- a/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 +++ b/tests/Microsoft.Windows.Assertion/Microsoft.Windows.Assertion.Tests.ps1 @@ -56,7 +56,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $SystemArchitectureResource.Architecture = 'Value' - { $SystemArchitectureResource.Set() } | Should -Throw + { $SystemArchitectureResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -90,7 +90,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $OsEditionResource.Edition = 'Value' - { $OsEditionResource.Set() } | Should -Throw + { $OsEditionResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -125,7 +125,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $ProcessorArchitectureResource.Architecture = 'Value' - { $ProcessorArchitectureResource.Set() } | Should -Throw + { $ProcessorArchitectureResource.Set() } | Should -Throw 'Assertion Failed. *' } } @@ -164,7 +164,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $HyperVisorResource.Ensure = 'Absent' - { $HyperVisorResource.Set() } | Should -Throw + { $HyperVisorResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -222,7 +222,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $OsInstallDateResource.Before = 'Friday, November 1, 2024 12:00:00 AM' - { $OsInstallDateResource.Set() } | Should -Throw + { $OsInstallDateResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -262,7 +262,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $OsVersionResource.MinVersion = '2.0.0' - { $OsVersionResource.Set() } | Should -Throw + { $OsVersionResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -296,7 +296,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $CsManufacturerResource.Manufacturer = 'Value' - { $CsManufacturerResource.Set() } | Should -Throw + { $CsManufacturerResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -330,7 +330,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $CsModelResource.Model = 'Value' - { $CsModelResource.Set() } | Should -Throw + { $CsModelResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -379,7 +379,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $CsDomainResource.Domain = 'Domain' - { $CsDomainResource.Set() } | Should -Throw + { $CsDomainResource.Set() } | Should -Throw 'Assertion Failed. *' } } } @@ -420,7 +420,7 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } It 'Should throw otherwise' { $PowerShellVersionResource.MinVersion = '7.2.1' - { $PowerShellVersionResource.Set() } | Should -Throw + { $PowerShellVersionResource.Set() } | Should -Throw 'Assertion Failed. *' } } @@ -429,7 +429,6 @@ InModuleScope -ModuleName Microsoft.Windows.Assertion { } } - Describe 'PnPDevice' { BeforeAll { From ba8b398c5af60742a5e5d4031590bff8595e9cc5 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Fri, 22 Nov 2024 20:52:15 -0600 Subject: [PATCH 15/16] Fix bracket error --- resources/Help/Microsoft.Windows.Assertion/PnPDevice.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md index 45e4ccec..b27ddecf 100644 --- a/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md +++ b/resources/Help/Microsoft.Windows.Assertion/PnPDevice.md @@ -21,9 +21,9 @@ The `PnPDevice` DSC Resource allows you to check for specific PnP Devices on the **Parameter**|**Attribute**|**DataType**|**Description**|**Allowed Values** :-----|:-----|:-----|:-----|:----- -`FriendlyName`|Optional|String[]|The name of the PnP Device to be found| +`FriendlyName`|Optional|String[]|The name of the PnP Device to be found|-- `DeviceClass`|Optional|String[]|The PnP Class of the PnP Device to be found.| For example: `Display` or `Keyboard` or `PrintQueue` -`Status`|Optional|String]]|The current status of the PnP Device to be found|`OK`, `ERROR`, `DEGRADED`, `UNKNOWN` +`Status`|Optional|String[]|The current status of the PnP Device to be found|`OK`, `ERROR`, `DEGRADED`, `UNKNOWN` ## EXAMPLES From 906f5036559745c5480d8b6f70814498ad1eaecf Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 16 Dec 2024 21:15:56 -0600 Subject: [PATCH 16/16] Try using latest version of Pester --- pipelines/azure-pipelines.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pipelines/azure-pipelines.yml b/pipelines/azure-pipelines.yml index aae4a474..12367095 100644 --- a/pipelines/azure-pipelines.yml +++ b/pipelines/azure-pipelines.yml @@ -81,7 +81,7 @@ extends: targetPath: $(Build.SourcesDirectory)\resources\Microsoft.DotNet.Dsc\ artifactName: Microsoft.DotNet.Dsc - output: pipelineArtifact - displayName: "Publish Pipeline Microsoft.Windows.Assertion" + displayName: 'Publish Pipeline Microsoft.Windows.Assertion' targetPath: $(Build.SourcesDirectory)\resources\Microsoft.Windows.Assertion\ artifactName: Microsoft.Windows.Assertion" @@ -96,6 +96,11 @@ extends: pwsh: true targetType: 'inline' script: | + $InstalledPesterModules = (Get-Module -ListAvailable -Name Pester) + if ($InstalledPesterModules -and $InstalledPesterModules[0].Version -lt [System.Version]::Parse('5.6.1')) { + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + Install-Module -Name Pester -RequiredVersion 5.6.1 -Force -SkipPublisherCheck + } $env:PSModulePath += ";$(Build.SourcesDirectory)\resources" Invoke-Pester -CI workingDirectory: $(Build.SourcesDirectory)\tests\