diff --git a/Functions/processPersistentItem.ps1 b/External/processPersistentItem-564a734 .ps1 similarity index 86% rename from Functions/processPersistentItem.ps1 rename to External/processPersistentItem-564a734 .ps1 index 246160b..bf4ae15 100644 --- a/Functions/processPersistentItem.ps1 +++ b/External/processPersistentItem-564a734 .ps1 @@ -39,20 +39,16 @@ function Invoke-ProcessPersistentItem [hashtable] $Properties, - [Parameter(ParameterSetName = 'with_properties', - Mandatory = $true)] - [string] - $PropertyGetter, - [Parameter(ParameterSetName = 'with_properties', Mandatory = $true)] [string] $PropertySetter, + [Parameter(ParameterSetName = 'with_properties', Mandatory = $true)] [string] - $PropertyNormalizer + $PropertyTester ) process { @@ -103,9 +99,8 @@ function Invoke-ProcessPersistentItem Mode = $Mode Keys = $_Keys Properties = $Properties - PropertyGetter = $PropertyGetter PropertySetter = $PropertySetter - PropertyNormalizer = $PropertyNormalizer + PropertyTester = $PropertyTester } Invoke-ProcessPersistentItemProperty @splat } @@ -129,17 +124,13 @@ function Invoke-ProcessPersistentItemProperty [hashtable] $Properties, - [Parameter(Mandatory = $true)] - [string] - $PropertyGetter, - [Parameter(Mandatory = $true)] [string] $PropertySetter, [Parameter(Mandatory = $true)] [string] - $PropertyNormalizer + $PropertyTester ) process { @@ -149,13 +140,10 @@ function Invoke-ProcessPersistentItemProperty # this is the desired value provided by the user $desired = $Properties.$propertyName - # normalize the desired value - $normalized = & $PropertyNormalizer -PropertyName $propertyName -Value $desired - - # get the existing value - $existing = & $PropertyGetter @_Keys -PropertyName $propertyName + # test for the desired value + $alreadyCorrect = & $PropertyTester @_Keys -PropertyName $propertyName -Value $desired - if ( $existing -ne $normalized ) + if ( -not $alreadyCorrect ) { if ( $Mode -eq 'Test' ) { diff --git a/Functions/loadTypes.ps1 b/Functions/loadTypes.ps1 index 462ca1d..4e827a5 100644 --- a/Functions/loadTypes.ps1 +++ b/Functions/loadTypes.ps1 @@ -3,6 +3,5 @@ 'shellLibraryType.ps1' 'shellLibraryFolderType.ps1' 'stockIconInfoType.ps1' - 'shortcutType.ps1' ) | % { . "$($PSCommandPath | Split-Path -Parent)\$_" } diff --git a/Functions/processPersistentItem.Tests.ps1 b/Functions/processPersistentItem.Tests.ps1 deleted file mode 100644 index 2a7bb34..0000000 --- a/Functions/processPersistentItem.Tests.ps1 +++ /dev/null @@ -1,312 +0,0 @@ -Import-Module WindowsShell -Force - -InModuleScope WindowsShell { - -function Get-PersistentItem { param ($Key) } -function Add-PersistentItem { param ($Key) } -function Remove-PersistentItem { param ($Key) } - -Describe 'Invoke-ProcessPersistentItem -Ensure Present: ' { - Mock Get-PersistentItem -Verifiable - Mock Add-PersistentItem -Verifiable - Mock Remove-PersistentItem { 'junk' } -Verifiable - Mock Invoke-ProcessPersistentItemProperty -Verifiable - - $delegates = @{ - Getter = 'Get-PersistentItem' - Adder = 'Add-PersistentItem' - Remover = 'Remove-PersistentItem' - PropertyGetter = 'Get-PersistentItemProperty' - PropertySetter = 'Set-PersistentItemProperty' - PropertyNormalizer = 'Get-NormalizedPersistentItemProperty' - } - $coreDelegates = @{ - Getter = 'Get-PersistentItem' - Adder = 'Add-PersistentItem' - Remover = 'Remove-PersistentItem' - } - - Context '-Ensure Present: absent, Set' { - Mock Add-PersistentItem { 'item' } - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{ P = 'P desired' } - } - $r = Invoke-ProcessPersistentItem Set Present @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 1 { - $Mode -eq 'Set' -and - $_Keys.Key -eq 'key value' -and - $Properties.P -eq 'P desired' -and - $PropertyGetter -eq 'Get-PersistentItemProperty' -and - $PropertySetter -eq 'Set-PersistentItemProperty' -and - $PropertyNormalizer -eq 'Get-NormalizedPersistentItemProperty' - } - } - } - Context '-Ensure Present: absent, Set - omitting properties skips setting properties' { - Mock Add-PersistentItem { 'item' } - It 'returns nothing' { - $splat = @{ Keys = @{ Key = 'key value' } } - $r = Invoke-ProcessPersistentItem Set Present @splat @coreDelegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Present: absent, Test' { - It 'returns false' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Test Present @splat @delegates - $r | Should be $false - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Present: present, Set' { - Mock Get-PersistentItem { 'item' } -Verifiable - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Set Present @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 1 - } - } - Context '-Ensure Present: present, Test' { - Mock Get-PersistentItem { 'item' } -Verifiable - Mock Invoke-ProcessPersistentItemProperty { 'property test result' } -Verifiable - It 'returns result of properties test' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Test Present @splat @delegates - $r | Should be 'property test result' - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 1 - } - } - Context '-Ensure Present: present, Test - omitting properties skips setting properties' { - Mock Get-PersistentItem { 'item' } -Verifiable - It 'returns result of properties test' { - $splat = @{ Keys = @{ Key = 'key value' } } - $r = Invoke-ProcessPersistentItem Test Present @splat @coreDelegates - $r | Should be $true - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Absent: absent, Set' { - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Set Absent @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Absent: absent, Test' { - It 'returns true' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Test Absent @splat @delegates - $r | Should be $true - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Absent: present, Set' { - Mock Get-PersistentItem { 'item' } -Verifiable - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Set Absent @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } - Context '-Ensure Absent: present, Test' { - Mock Get-PersistentItem { 'item' } -Verifiable - It 'returns false' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{} - } - $r = Invoke-ProcessPersistentItem Test Absent @splat @delegates - $r | Should be $false - } - It 'correctly invokes functions' { - Assert-MockCalled Get-PersistentItem 1 { $Key -eq 'key value' } - Assert-MockCalled Add-PersistentItem 0 -Exactly - Assert-MockCalled Remove-PersistentItem 0 -Exactly - Assert-MockCalled Invoke-ProcessPersistentItemProperty 0 -Exactly - } - } -} - - -function Get-PersistentItemProperty { param ($Key,$PropertyName) } -function Set-PersistentItemProperty { param ($Key,$PropertyName,$Value) } -function Get-NormalizedPersistentItemProperty { param ($PropertyName,$Value) } - -Describe 'Invoke-ProcessPersistentItemProperty' { - Mock Get-NormalizedPersistentItemProperty -Verifiable - Mock Get-PersistentItemProperty -Verifiable - Mock Set-PersistentItemProperty { 'junk' } -Verifiable - - $delegates = @{ - PropertyGetter = 'Get-PersistentItemProperty' - PropertySetter = 'Set-PersistentItemProperty' - PropertyNormalizer = 'Get-NormalizedPersistentItemProperty' - } - Context 'Set, property already correct' { - Mock Get-NormalizedPersistentItemProperty { 'already correct' } -Verifiable - Mock Get-PersistentItemProperty { 'already correct' } -Verifiable - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{ P = 'already correct' } - } - $r = Invoke-ProcessPersistentItemProperty Set @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-NormalizedPersistentItemProperty 1 { - $PropertyName -eq 'P' -and - $Value -eq 'already correct' - } - Assert-MockCalled Get-PersistentItemProperty 1 { - $Key -eq 'key value' -and - $PropertyName -eq 'P' - } - Assert-MockCalled Set-PersistentItemProperty 0 -Exactly - } - } - Context 'Test, property correct' { - Mock Get-NormalizedPersistentItemProperty { 'correct' } -Verifiable - Mock Get-PersistentItemProperty { 'correct' } -Verifiable - It 'returns true' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{ P = 'correct' } - } - $r = Invoke-ProcessPersistentItemProperty Test @splat @delegates - $r | Should be $true - } - It 'correctly invokes functions' { - Assert-MockCalled Get-NormalizedPersistentItemProperty 1 { - $PropertyName -eq 'P' -and - $Value -eq 'correct' - } - Assert-MockCalled Get-PersistentItemProperty 1 { - $Key -eq 'key value' -and - $PropertyName -eq 'P' - } - Assert-MockCalled Set-PersistentItemProperty 0 -Exactly - } - } - Context 'Set, correcting property' { - Mock Get-NormalizedPersistentItemProperty { 'normalized' } -Verifiable - Mock Get-PersistentItemProperty { 'original' } -Verifiable - It 'returns nothing' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{ P = 'desired' } - } - $r = Invoke-ProcessPersistentItemProperty Set @splat @delegates - $r | Should beNullOrEmpty - } - It 'correctly invokes functions' { - Assert-MockCalled Get-NormalizedPersistentItemProperty 1 { - $PropertyName -eq 'P' -and - $Value -eq 'desired' - } - Assert-MockCalled Get-PersistentItemProperty 1 { - $Key -eq 'key value' -and - $PropertyName -eq 'P' - } - Assert-MockCalled Set-PersistentItemProperty 1 -Exactly { - $Key -eq 'key value' -and - $PropertyName -eq 'P' -and - $Value -eq 'desired' - } - } - } - Context 'Test, property incorrect' { - Mock Get-NormalizedPersistentItemProperty { 'normalized' } -Verifiable - Mock Get-PersistentItemProperty { 'original' } -Verifiable - It 'returns false' { - $splat = @{ - Keys = @{ Key = 'key value' } - Properties = @{ P = 'desired' } - } - $r = Invoke-ProcessPersistentItemProperty Test @splat @delegates - $r | Should be $false - } - It 'correctly invokes functions' { - Assert-MockCalled Get-NormalizedPersistentItemProperty 1 { - $PropertyName -eq 'P' -and - $Value -eq 'desired' - } - Assert-MockCalled Get-PersistentItemProperty 1 { - $Key -eq 'key value' -and - $PropertyName -eq 'P' - } - Assert-MockCalled Set-PersistentItemProperty 0 -Exactly - } - } -} -} diff --git a/Functions/processShellLibrary.Tests.ps1 b/Functions/processShellLibrary.Tests.ps1 index 3778f94..937c37b 100644 --- a/Functions/processShellLibrary.Tests.ps1 +++ b/Functions/processShellLibrary.Tests.ps1 @@ -63,7 +63,32 @@ Describe 'Invoke-ProcessShellLibrary' { Mode = 'Set' Ensure = 'Present' Name = 'name' - TypeName = 'DoNotSet' + } + $r = $params | Invoke-ProcessShellLibrary + $r.Count | Should be 1 + $r | Should be 'return value' + } + It 'correctly invokes functions' { + Assert-MockCalled Invoke-ProcessPersistentItem 1 { + $Mode -eq 'Set' -and + $Ensure -eq 'Present' -and + $_Keys.Name -eq 'name' -and + + #Properties + $Properties.Count -eq 0 + } + } + } + Context 'null optional' { + It 'returns exactly one item' { + $params = New-Object psobject -Property @{ + Mode = 'Set' + Ensure = 'Present' + Name = 'name' + TypeName = $null + StockIconName = $null + IconFilePath = $null + IconResourceId = $null } $r = $params | Invoke-ProcessShellLibrary $r.Count | Should be 1 diff --git a/Functions/processShellLibrary.ps1 b/Functions/processShellLibrary.ps1 index 2b3ea67..529d418 100644 --- a/Functions/processShellLibrary.ps1 +++ b/Functions/processShellLibrary.ps1 @@ -34,42 +34,39 @@ function Invoke-ProcessShellLibrary [Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Set','Test')] + [System.Nullable[Mode]] $Mode, [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Present','Absent')] + [System.Nullable[Ensure]] $Ensure = 'Present', [Parameter(Mandatory = $true, Position = 3, - ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [ValidateScript({ $_ | Test-ValidShellLibraryName })] [Alias('LibraryName')] - [string] $Name, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] + [System.Nullable[LibraryTypeName]] $TypeName, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] + [System.Nullable[StockIconName]] $StockIconName, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] $IconFilePath, [Parameter(ValueFromPipelineByPropertyName = $true)] - [int] - $IconResourceId=0 + [System.Nullable[int]] + $IconResourceId ) process { # validate parameters + $Name | ? {$_} | Test-ValidShellLibraryName -ea Stop | Out-Null $IconFilePath | ? {$_} | Test-ValidFilePath -ea Stop | Out-Null $TypeName | ? {$_} | Test-ValidShellLibraryTypeName -ea Stop | Out-Null $StockIconName | ? {$_} | Test-ValidStockIconName -ea Stop | Out-Null @@ -77,8 +74,7 @@ function Invoke-ProcessShellLibrary # pass through properties $properties = @{} 'TypeName' | - ? { $_ -in $PSCmdlet.MyInvocation.BoundParameters.Keys } | - ? { (Get-Variable $_ -ValueOnly) -ne 'DoNotSet' } | + ? { $null -ne (Get-Variable $_ -ValueOnly) } | % { $properties.$_ = Get-Variable $_ -ValueOnly } @@ -102,9 +98,8 @@ function Invoke-ProcessShellLibrary Getter = 'Get-ShellLibrary' Adder = 'Add-ShellLibrary' Remover = 'Remove-ShellLibrary' - PropertyGetter = 'Get-ShellLibraryProperty' PropertySetter = 'Set-ShellLibraryProperty' - PropertyNormalizer = 'Get-NormalizedShellLibraryProperty' + PropertyTester = 'Test-ShellLibraryProperty' } Invoke-ProcessPersistentItem @splat } diff --git a/Functions/processShellLibraryFolder.ps1 b/Functions/processShellLibraryFolder.ps1 index 032604f..b0f7d68 100644 --- a/Functions/processShellLibraryFolder.ps1 +++ b/Functions/processShellLibraryFolder.ps1 @@ -6,32 +6,33 @@ function Invoke-ProcessShellLibraryFolder [Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Set','Test')] + [System.Nullable[Mode]] $Mode, [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Present','Absent')] + [System.Nullable[Ensure]] $Ensure = 'Present', [Parameter(Mandatory = $true, Position = 3, ValueFromPipelineByPropertyName = $true)] - [ValidateScript({ $_ | Test-ValidShellLibraryName })] - [Alias('Name')] [string] + [Alias('Name')] $LibraryName, [Parameter(Mandatory = $true, Position = 4, - ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [ValidateScript({ $_ | Test-ValidFilePath })] - [string] + [string[]] $FolderPath ) process { + # validate parameters + $LibraryName | ? {$_} | Test-ValidShellLibraryName -ea Stop | Out-Null + $FolderPath | ? {$_} | Test-ValidFilePath -ea Stop | Out-Null + $splat = @{ Mode = $Mode Ensure = $Ensure diff --git a/Functions/processShortcut.Tests.ps1 b/Functions/processShortcut.Tests.ps1 index a80427c..9a2bf11 100644 --- a/Functions/processShortcut.Tests.ps1 +++ b/Functions/processShortcut.Tests.ps1 @@ -18,7 +18,7 @@ Describe 'Invoke-ProcessShortcut' { WorkingDirectory = 'working directory' WindowStyle = 'Normal' Hotkey = 'hotkey' - StockIconName = 'stock icon name' + StockIconName = 'Folder' IconFilePath = 'icon file path' IconResourceId = 1 Description = 'description' @@ -29,7 +29,7 @@ Describe 'Invoke-ProcessShortcut' { } It 'correctly invokes functions' { Assert-MockCalled Get-IconReferencePath 1 { - $StockIconName -eq 'stock icon name' -and + $StockIconName -eq 'Folder' -and $IconFilePath -eq 'icon file path' -and $IconResourceId -eq 1 } @@ -66,6 +66,38 @@ Describe 'Invoke-ProcessShortcut' { $Ensure -eq 'Present' -and $_Keys.Path -eq 'path' -and + #Properties + $Properties.Count -eq 0 + } + } + } + Context 'null optional' { + Mock Invoke-ProcessPersistentItem { 'return value' } -Verifiable + It 'returns exactly one item' { + $params = New-Object psobject -Property @{ + Mode = 'Set' + Ensure = 'Present' + Path = 'path' + TargetPath = $null + Arguments = $null + WorkingDirectory = $null + WindowStyle = $null + Hotkey = $null + StockIconName = $null + IconFilePath = $null + IconResourceId = $null + Description = $null + } + $r = $params | Invoke-ProcessShortcut + $r.Count | Should be 1 + $r | Should be 'return value' + } + It 'correctly invokes functions' { + Assert-MockCalled Invoke-ProcessPersistentItem 1 { + $Mode -eq 'Set' -and + $Ensure -eq 'Present' -and + $_Keys.Path -eq 'path' -and + #Properties $Properties.Count -eq 0 } diff --git a/Functions/processShortcut.ps1 b/Functions/processShortcut.ps1 index 7604218..e334600 100644 --- a/Functions/processShortcut.ps1 +++ b/Functions/processShortcut.ps1 @@ -6,60 +6,53 @@ function Invoke-ProcessShortcut [Parameter(Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Set','Test')] + [System.Nullable[Mode]] $Mode, [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)] - [ValidateSet('Present','Absent')] + [System.Nullable[Ensure]] $Ensure = 'Present', [Parameter(Mandatory = $true, Position = 3, ValueFromPipelineByPropertyName = $true)] [Alias('FullName')] - [string] $Path, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Target')] - [string] $TargetPath, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] $Arguments, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('StartIn','WorkingFolder')] - [string] $WorkingDirectory, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Run')] - [WindowStyle] + [System.Nullable[WindowStyle]] $WindowStyle, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('ShortcutKey')] - [string] $Hotkey, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] + [System.Nullable[StockIconName]] $StockIconName, [Parameter(ValueFromPipelineByPropertyName = $true)] - [string] $IconFilePath, [Parameter(ValueFromPipelineByPropertyName = $true)] - [int] - $IconResourceId=0, + [System.Nullable[int]] + $IconResourceId, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Comment')] - [string] $Description ) process @@ -68,7 +61,7 @@ function Invoke-ProcessShortcut $properties = @{} 'TargetPath','Arguments','WorkingDirectory', 'WindowStyle','Hotkey','Description' | - ? { $_ -in $PSCmdlet.MyInvocation.BoundParameters.Keys } | + ? { Get-Variable $_ -ValueOnly } | % { $properties.$_ = Get-Variable $_ -ValueOnly } @@ -92,9 +85,8 @@ function Invoke-ProcessShortcut Getter = 'Get-ShortCut' Adder = 'Add-ShortCut' Remover = 'Remove-ShortCut' - PropertyGetter = 'Get-ShortCutProperty' PropertySetter = 'Set-ShortCutProperty' - PropertyNormalizer = 'Get-NormalizedShortCutProperty' + PropertyTest = 'Test-ShortCutProperty' } Invoke-ProcessPersistentItem @splat } diff --git a/Functions/shellLibrary.ps1 b/Functions/shellLibrary.ps1 index 6235217..da46b83 100644 --- a/Functions/shellLibrary.ps1 +++ b/Functions/shellLibrary.ps1 @@ -235,25 +235,30 @@ function Set-ShellLibraryProperty } } -function Get-NormalizedShellLibraryProperty +function Test-ShellLibraryProperty { [CmdletBinding()] param ( [Parameter(Mandatory = $true, - Position = 1)] + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true)] + [string] + [Alias('Key','Name')] + $LibraryName, + + [Parameter(Mandatory = $true, + position = 1)] + [ValidateSet('TypeName','IconReferencePath')] [string] $PropertyName, [Parameter(Mandatory = $true, - Position = 2, - ValueFromPipeline = $true)] - [AllowNull()] - [AllowEmptyString()] + position = 2)] $Value ) process { - $Value + $Value -eq (Get-ShellLibraryProperty -LibraryName $LibraryName -PropertyName $PropertyName) } } diff --git a/Functions/shortcut.ps1 b/Functions/shortcut.ps1 index 1f0f8df..6f62363 100644 --- a/Functions/shortcut.ps1 +++ b/Functions/shortcut.ps1 @@ -219,3 +219,31 @@ function Get-NormalizedShortcutProperty return $shortcut.$PropertyName } } + +function Test-ShortcutProperty +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true)] + [string] + $Path, + + [Parameter(Mandatory = $true, + position = 1)] + [string] + $PropertyName, + + [Parameter(Mandatory = $true, + position = 2)] + $Value + ) + process + { + $actualValue = Get-ShortcutProperty -Path $Path -PropertyName $PropertyName + $normalizedValue = Get-NormalizedShortcutProperty -PropertyName $PropertyName -Value $Value + return $actualValue -eq $normalizedValue + } +} diff --git a/IntegrationTests/shellLibraryFolderResource.Tests.ps1 b/IntegrationTests/shellLibraryFolderResource.Tests.ps1 index 0990099..4b653b8 100644 --- a/IntegrationTests/shellLibraryFolderResource.Tests.ps1 +++ b/IntegrationTests/shellLibraryFolderResource.Tests.ps1 @@ -1,4 +1,4 @@ -Remove-Module WindowsShell -fo -ea si; Import-Module WindowsShell +Import-Module WindowsShell -Force Import-Module PSDesiredStateConfiguration Describe 'ShellLibraryFolder Resource' { @@ -27,8 +27,8 @@ Describe 'ShellLibraryFolder Resource' { %{ $_ | Should be $true } } It 'create the library' { - $libraryName | Invoke-ProcessShellLibrary Set - $r = $libraryName | Invoke-ProcessShellLibrary Test + Invoke-ProcessShellLibrary Set -Name $libraryName + $r = Invoke-ProcessShellLibrary Test -Name $libraryName $r | Should be $true } } @@ -86,8 +86,8 @@ Describe 'ShellLibraryFolder Resource' { Test-Path $folderPath1 | Should be $false } It 'remove the library' { - $libraryName | Invoke-ProcessShellLibrary Set Absent - $r = $libraryName | Invoke-ProcessShellLibrary Test Absent + Invoke-ProcessShellLibrary Set Absent -Name $libraryName + $r = Invoke-ProcessShellLibrary Test Absent -Name $libraryName $r | Should be $true } } diff --git a/IntegrationTests/shellLibraryResource.Tests.ps1 b/IntegrationTests/shellLibraryResource.Tests.ps1 index 6efa106..2619cd7 100644 --- a/IntegrationTests/shellLibraryResource.Tests.ps1 +++ b/IntegrationTests/shellLibraryResource.Tests.ps1 @@ -1,4 +1,4 @@ -Remove-Module WindowsShell -fo -ea si; Import-Module WindowsShell +Import-Module WindowsShell -Force Import-Module PSDesiredStateConfiguration Describe 'ShellLibrary Resource' { diff --git a/IntegrationTests/zeroDsc.shellLibrary.Tests.ps1 b/IntegrationTests/zeroDsc.shellLibrary.Tests.ps1 index 9a6d4c8..f935bfb 100644 --- a/IntegrationTests/zeroDsc.shellLibrary.Tests.ps1 +++ b/IntegrationTests/zeroDsc.shellLibrary.Tests.ps1 @@ -3,7 +3,7 @@ if ( -not (Get-Module ZeroDsc -ListAvailable) ) return } -Remove-Module WindowsShell -fo -ea si; Import-Module WindowsShell +Import-Module WindowsShell -Force Import-Module PSDesiredStateConfiguration, ZeroDsc Describe 'Invoke with ZeroDsc (ShellLibrary)' { @@ -18,14 +18,13 @@ Describe 'Invoke with ZeroDsc (ShellLibrary)' { } } Context ShellLibrary { - $document = [scriptblock]::Create(@" - Get-DscResource ShellLibrary WindowsShell | Import-DscResource - ShellLibrary MyLib @{ Name = '$libraryName1' } -"@ - ) $h = @{} It 'create instructions' { - $h.i = ConfigInstructions SomeName $document + $h.i = ConfigInstructions SomeName { + $r = Get-DscResource ShellLibrary WindowsShell + $r | Import-DscResource + ShellLibrary MyLib @{ Name = $libraryName1 } + } } foreach ( $step in $h.i ) { @@ -36,17 +35,15 @@ Describe 'Invoke with ZeroDsc (ShellLibrary)' { } } Context ShellLibraryFolder { - $document = [scriptblock]::Create(@" - Get-DscResource ShellLibraryFolder WindowsShell | Import-DscResource - ShellLibraryFolder MyDir @{ - LibraryName = '$libraryName1' - FolderPath = '$folderPath' - } -"@ - ) $h = @{} It 'create instructions' { - $h.i = ConfigInstructions SomeName $document + $h.i = ConfigInstructions SomeName { + Get-DscResource ShellLibraryFolder WindowsShell | Import-DscResource + ShellLibraryFolder MyDir @{ + LibraryName = $libraryName1 + FolderPath = $folderPath + } + } } foreach ( $step in $h.i ) { @@ -57,19 +54,17 @@ Describe 'Invoke with ZeroDsc (ShellLibrary)' { } } Context 'combined' { - $document = [scriptblock]::Create(@" - Get-DscResource -Module WindowsShell | Import-DscResource - ShellLibraryFolder MyDir @{ - LibraryName = '$libraryName2' - FolderPath = '$folderPath' - DependsOn = '[ShellLibrary]MyLib' - } - ShellLibrary MyLib @{ Name = '$libraryName2' } -"@ - ) - $h = @{} + $h = @{} It 'create instructions' { - $h.i = ConfigInstructions SomeName $document + $h.i = ConfigInstructions SomeName { + Get-DscResource -Module WindowsShell | Import-DscResource + ShellLibraryFolder MyDir @{ + LibraryName = $libraryName2 + FolderPath = $folderPath + DependsOn = '[ShellLibrary]MyLib' + } + ShellLibrary MyLib @{ Name = $libraryName2 } + } } foreach ( $step in $h.i ) { @@ -85,8 +80,8 @@ Describe 'Invoke with ZeroDsc (ShellLibrary)' { Test-Path $folderPath | Should be $false } It 'remove the libraries' { - $libraryName1,$libraryName2 | Invoke-ProcessShellLibrary Set Absent - ( $libraryName1,$libraryName2 | Invoke-ProcessShellLibrary Test Absent ) -ne + $libraryName1,$libraryName2 | % { Invoke-ProcessShellLibrary Set Absent -Name $_ } + ( $libraryName1,$libraryName2 | % { Invoke-ProcessShellLibrary Test Absent -Name $_ } ) -ne $true | Should beNullOrEmpty } diff --git a/IntegrationTests/zeroDsc.shortcut.Tests.ps1 b/IntegrationTests/zeroDsc.shortcut.Tests.ps1 index ced1e12..8acc0c5 100644 --- a/IntegrationTests/zeroDsc.shortcut.Tests.ps1 +++ b/IntegrationTests/zeroDsc.shortcut.Tests.ps1 @@ -3,7 +3,7 @@ if ( -not (Get-Module ZeroDsc -ListAvailable) ) return } -Remove-Module WindowsShell -fo -ea si; Import-Module WindowsShell +Import-Module WindowsShell -Force Import-Module PSDesiredStateConfiguration, ZeroDsc Describe 'Invoke with ZeroDsc (Shortcut)' { @@ -13,14 +13,14 @@ Describe 'Invoke with ZeroDsc (Shortcut)' { $shortcutPath = Join-Path $tempPath $shortcutFilename $tests = [ordered]@{ - basic = @" + basic = { Get-DscResource Shortcut WindowsShell | Import-DscResource - Shortcut MyShortcut @{ Path = "$shortcutPath" } -"@ - full = @" + Shortcut MyShortcut @{ Path = $shortcutPath } + } + full = { Get-DscResource Shortcut WindowsShell | Import-DscResource Shortcut MyShortcut @{ - Path = "$shortcutPath" + Path = $shortcutPath Arguments = 'arguments' Hotkey = 'Ctrl+Alt+f' StockIconName = 'AudioFiles' @@ -29,12 +29,12 @@ Describe 'Invoke with ZeroDsc (Shortcut)' { WorkingDirectory = 'c:\temp' Description = 'some description' } -"@ + } } foreach ( $testName in $tests.Keys ) { Context $testName { - $document = [scriptblock]::Create($tests.$testName) + $document = $tests.$testName $h = @{} It 'create instructions' { $h.i = ConfigInstructions SomeName $document diff --git a/ShellLibrary.psm1 b/ShellLibrary.psm1 index 18d5cf6..3b606c3 100644 --- a/ShellLibrary.psm1 +++ b/ShellLibrary.psm1 @@ -1,62 +1,28 @@ -enum Ensure -{ - Present - Absent -} - -enum LibraryTypeName -{ - Generic - Documents - Music - Pictures - Videos - - DoNotSet -} - -enum StockIconName -{ - DocumentNotAssociated; DocumentAssociated; Application; Folder; FolderOpen; Drive525; Drive35; DriveRemove; - DriveFixed; DriveNetwork; DriveNetworkDisabled; DriveCD; DriveRam; World; Server; Printer; MyNetwork; Find; Help; - Share; Link; SlowFile; Recycler; RecyclerFull; MediaCDAudio; Lock; AutoList; PrinterNet; ServerShare; PrinterFax; - PrinterFaxNet; PrinterFile; Stack; MediaSvcd; StuffedFolder; DriveUnknown; DriveDvd; MediaDvd; MediaDvdRam; - MediaDvdRW; MediaDvdR; MediaDvdRom; MediaCDAudioPlus; MediaCDRW; MediaCDR; MediaCDBurn; MediaBlankCD; MediaCDRom; - AudioFiles; ImageFiles; VideoFiles; MixedFiles; FolderBack; FolderFront; Shield; Warning; Info; Error; Key; - Software; Rename; Delete; MediaAudioDvd; MediaMovieDvd; MediaEnhancedCD; MediaEnhancedDvd; MediaHDDvd; - MediaBluRay; MediaVcd; MediaDvdPlusR; MediaDvdPlusRW; DesktopPC; MobilePC; Users; MediaSmartMedia; - MediaCompactFlash; DeviceCellPhone; DeviceCamera; DeviceVideoCamera; DeviceAudioPlayer; NetworkConnect; Internet; - ZipFile; Settings; DriveHDDVD; DriveBluRay; MediaHDDVDROM; MediaHDDVDR; MediaHDDVDRAM; MediaBluRayROM; - MediaBluRayR; MediaBluRayRE; ClusteredDisk; - - DoNotSet -} - [DscResource()] class ShellLibrary { - [DscProperty(Key)] + [DscProperty(Key,Mandatory)] [string] $Name [DscProperty()] - [Ensure] - $Ensure + [System.Nullable[Ensure]] + $Ensure = 'Present' [DscProperty()] - [LibraryTypeName] - $TypeName = [LibraryTypeName]::DoNotSet + [System.Nullable[LibraryTypeName]] + $TypeName [DscProperty()] - [StockIconName] - $StockIconName = [StockIconName]::DoNotSet + [System.Nullable[StockIconName]] + $StockIconName [DscProperty()] [string] $IconFilePath [DscProperty()] - [int] + [System.Nullable[int]] $IconResourceId [void] Set() { $this | Invoke-ProcessShellLibrary Set } diff --git a/ShellLibraryFolder.psm1 b/ShellLibraryFolder.psm1 index 2dcc436..97b4a32 100644 --- a/ShellLibraryFolder.psm1 +++ b/ShellLibraryFolder.psm1 @@ -1,13 +1,7 @@ -enum Ensure -{ - Present - Absent -} - [DscResource()] class ShellLibraryFolder { - [DscProperty(Key)] + [DscProperty(Key,Mandatory)] [string] $LibraryName @@ -16,18 +10,18 @@ class ShellLibraryFolder $FolderPath [DscProperty()] - [Ensure] - $Ensure + [System.Nullable[Ensure]] + $Ensure = 'Present' [void] Set() { - $this.FolderPath | - Invoke-ProcessShellLibraryFolder Set $this.Ensure $this.LibraryName + $this.FolderPath | + % { $this | Invoke-ProcessShellLibraryFolder Set -FolderPath $_ } } [bool] Test() { $numSucceeded = $this.FolderPath | - Invoke-ProcessShellLibraryFolder Test $this.Ensure $this.LibraryName | + % { $this | Invoke-ProcessShellLibraryFolder Test -FolderPath $_ } | ? { $_ -eq $true } | Measure-Object | % Count diff --git a/Shortcut.psm1 b/Shortcut.psm1 index 2dda092..bd79bef 100644 --- a/Shortcut.psm1 +++ b/Shortcut.psm1 @@ -1,43 +1,13 @@ -enum Ensure -{ - Present - Absent -} - -enum WindowStyle -{ - Normal = 1 - Maximized = 3 - Minimized = 7 -} - -enum StockIconName -{ - DocumentNotAssociated; DocumentAssociated; Application; Folder; FolderOpen; Drive525; Drive35; DriveRemove; - DriveFixed; DriveNetwork; DriveNetworkDisabled; DriveCD; DriveRam; World; Server; Printer; MyNetwork; Find; Help; - Share; Link; SlowFile; Recycler; RecyclerFull; MediaCDAudio; Lock; AutoList; PrinterNet; ServerShare; PrinterFax; - PrinterFaxNet; PrinterFile; Stack; MediaSvcd; StuffedFolder; DriveUnknown; DriveDvd; MediaDvd; MediaDvdRam; - MediaDvdRW; MediaDvdR; MediaDvdRom; MediaCDAudioPlus; MediaCDRW; MediaCDR; MediaCDBurn; MediaBlankCD; MediaCDRom; - AudioFiles; ImageFiles; VideoFiles; MixedFiles; FolderBack; FolderFront; Shield; Warning; Info; Error; Key; - Software; Rename; Delete; MediaAudioDvd; MediaMovieDvd; MediaEnhancedCD; MediaEnhancedDvd; MediaHDDvd; - MediaBluRay; MediaVcd; MediaDvdPlusR; MediaDvdPlusRW; DesktopPC; MobilePC; Users; MediaSmartMedia; - MediaCompactFlash; DeviceCellPhone; DeviceCamera; DeviceVideoCamera; DeviceAudioPlayer; NetworkConnect; Internet; - ZipFile; Settings; DriveHDDVD; DriveBluRay; MediaHDDVDROM; MediaHDDVDR; MediaHDDVDRAM; MediaBluRayROM; - MediaBluRayR; MediaBluRayRE; ClusteredDisk; - - DoNotSet -} - [DscResource()] class Shortcut { - [DscProperty(Key)] + [DscProperty(Key,Mandatory)] [string] $Path [DscProperty()] - [Ensure] - $Ensure + [System.Nullable[Ensure]] + $Ensure = 'Present' [DscProperty()] [string] @@ -52,23 +22,23 @@ class Shortcut $WorkingDirectory [DscProperty()] - [WindowStyle] - $WindowStyle=[WindowStyle]::Normal + [System.Nullable[WindowStyle]] + $WindowStyle [DscProperty()] [string] $Hotkey [DscProperty()] - [StockIconName] - $StockIconName = [StockIconName]::DoNotSet + [System.Nullable[StockIconName]] + $StockIconName [DscProperty()] [string] $IconFilePath [DscProperty()] - [int] + [System.Nullable[int]] $IconResourceId [DscProperty()] diff --git a/TestFunctions/resourcePlumbing.ps1 b/TestFunctions/resourcePlumbing.ps1 new file mode 100644 index 0000000..7aea1d5 --- /dev/null +++ b/TestFunctions/resourcePlumbing.ps1 @@ -0,0 +1,475 @@ +function Test-ResourceObject +{ + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 1)] + $ResourceName, + + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 2)] + $ModuleName + ) + Describe "resource object $ResourceName in module $ModuleName" { + Context 'module and resource registration' { + It "$ModuleName is a module available with Get-Module" { + $r = Get-Module $ModuleName -ListAvailable + $r | Should not beNullOrEmpty + } + It "$ModuleName successfully imports" { + Import-Module $ModuleName + } + It "$ResourceName is a nested module of $ModuleName" { + $r = (Get-Module $ModuleName).NestedModules | + ? { $_.Name -eq $ResourceName } + $r | Should not beNullOrEmpty + $r.Count | Should be 1 + } + It "$ResourceName in $ModuleName is a DscResource available with Get-DscResource" { + $r = Get-DscResource $ResourceName $ModuleName + $r | Should not beNullOrEmpty + } + } + $modulePath = (Get-Module $ModuleName).NestedModules | + ? { $_.Name -eq $ResourceName } | + select -First 1 | + % Path + $h = @{} + Context 'object' { + It "import $modulePath" { + Import-Module $modulePath + } + It "module $ResourceName is a module availabe with Get-Module" { + $r = Get-Module $ResourceName + $r | Should not beNullOrEmpty + } + It "an object of type $ResourceName can be created inside $ResourceName module" { + $h.o = & (Get-Module $ResourceName).NewBoundScriptBlock( + [scriptblock]::Create("New-Object $ResourceName") + ) + $h.o | Should not beNullOrEmpty + } + It 'the object''s type has the DscResourceAttribute' { + $r = $h.o.GetType().CustomAttributes | + ? { $_.AttributeType -eq ([System.Management.Automation.DscResourceAttribute]) } + $r | Should not beNullOrEmpty + } + } + Context 'member functions' { + foreach ( $values in @( + @('Get',"$ResourceName Get()"), + @('Set',"void Set()"), + @('Test',"bool Test()") + )) + { + $methodName,$signature = $values + It "the object has a .$methodName() method" { + $r = Get-Member -InputObject $h.o -MemberType Method -Name $methodName + $r | Should not beNullOrEmpty + } + It "the method signature is `"$signature`"" { + $r = Get-Member -InputObject $h.o -MemberType Method -Name $methodName | + % Definition + $r | Should be $signature + } + } + } + Context 'member variables' { + It 'has member variables' { + $r = $h.o.GetType().GetProperties() + $r | Should not beNullOrEmpty + } + It 'has member variables with the DscProperty() attribute' { + $r = $h.o.GetType().GetProperties() | + % CustomAttributes | + ? { $_.AttributeType -eq ([System.Management.Automation.DscPropertyAttribute]) } + $r | Should not beNullOrEmpty + } + It 'has at least one DSC property that is a key' { + $r = $h.o.GetType().GetProperties() | + % CustomAttributes | + ? { $_.AttributeType -eq ([System.Management.Automation.DscPropertyAttribute]) } | + % NamedArguments | + ? { $_.MemberName -eq 'Key' } + $r | Should not beNullOrEmpty + } + foreach ( $propertyName in $h.o | Get-Member -MemberType Property | % Name ) + { + if ( $propertyName -eq 'Ensure' ) + { + It 'Ensure : has default value of "Present"' { + $h.o.Ensure | Should be 'Present' + } + } + else + { + It "$propertyName : default value is null" { + $null -eq $h.o.$propertyName | Should be $true + } + } + } + } + if ( Get-Member -InputObject $h.o -MemberType Property -Name 'Ensure' ) + { + Context 'Ensure member variable' { + It 'has a default value of "Present"' { + $h.o.Ensure | Should be 'Present' + } + } + } + Context 'cleanup' { + # Removing module causing problems with mocking later. Should test with removal again + # once PowerShell GH-2505 is fixed. + It "remove module $ResourceName" { + #Remove-Module $ResourceName -ea Stop + } + It "module $ResourceName is no longer available with Get-Module" { + #$r = Get-Module $ResourceName + #$r | Should beNullOrEmpty + } + } + } +} + +function Test-ProcessFunction +{ + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 1)] + $ModuleName, + + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 2)] + $FunctionName + ) + + Describe "function $FunctionName in module $ModuleName" { + Context 'module and function registration' { + It "$ModuleName is a module available with Get-Module" { + $r = Get-Module $ModuleName -ListAvailable + $r | Should not beNullOrEmpty + } + It "$ModuleName successfully imports" { + Import-Module $ModuleName + } + It "$ModuleName exports function $FunctionName" { + $r = (Get-Module $ModuleName).ExportedFunctions.$FunctionName + $r | Should not beNullOrEmpty + } + } + $function = (Get-Module $ModuleName).ExportedFunctions.$FunctionName + foreach ( $values in @( + @('Mode', 'design_requires', 'mandatory', $null, 1, 'System.Nullable[Mode]'), + @('Ensure', $null, $null, 'Present',2, 'System.Nullable[Ensure]') + )) + { + $name,$designRequires,$mandatory,$defaultValue,$position,$typeName = $values + + if ( $designRequires -or + $function.Parameters.get_keys() -contains $name ) + { + Context "$name parameter" { + It "the function has a $name parameter" { + $r = $function.Parameters.$name + $r | Should not beNullOrEmpty + } + if ($mandatory) { + It 'is mandatory' { + $r = $function.Parameters.$name.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } | + % Mandatory + $r | Should be $true + } + } + if ( $defaultValue ) + { + It "has default value $defaultValue" { + $r = $function.ScriptBlock.Ast.Body.ParamBlock.Parameters. + Where({$_.Name.VariablePath.UserPath -eq $name}). + DefaultValue.SafeGetValue() + $r | Should be $defaultValue + } + } + else + { + It 'does not have a default value' { + $r = $function.ScriptBlock.Ast.Body.ParamBlock.Parameters. + Where({$_.Name.VariablePath.UserPath -eq $name}). + DefaultValue + $r | Should beNullOrEmpty + } + } + It "is a positional argument" { + $r = $function.Parameters.$name.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } | + % Position + $r | Should BeGreaterThan -1 + } + It "takes position $position" { + $positions = $function.Parameters.Values | + % Attributes | + ? {$_.TypeId.Name -eq 'ParameterAttribute' } | + % Position | + ? { $_ -ge 0 } | + Sort + $expected = $positions[$position-1] + + $actual = $function.Parameters.$name.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } | + % Position + + $actual | Should be $expected + } + It "is of type $typeName" { + $r = $function.Parameters.$name.ParameterType + $r | Should be $typeName + } + } + } + } + foreach ( $parameter in ($function.Parameters.Values | ? {$_.Name -notin (Get-CommonParameterNames)}) ) + { + Context "All Parameters: $($parameter.Name)" { + It 'does not bind to values from pipeline' { + $r = $parameter.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } + $r.ValueFromPipeline | Should be $false + } + It 'binds to properties of objects in pipeline by property name' { + $r = $parameter.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } + $r.ValueFromPipelineByPropertyName | Should be $true + } + + $parameter_ = $function.ScriptBlock.Ast.Body.ParamBlock.Parameters. + Where({$_.Name.VariablePath.UserPath -eq $($parameter.name)}) + + if ( $parameter_.StaticType.IsValueType ) + { + It 'is a Nullable value type' { + $r = $parameter_.StaticType.Name + $r | Should be 'Nullable`1' + } + } + elseif ( $function.Parameters.$($parameter.Name).Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } | + % Mandatory ) + { + It 'has a static type' { + $parameter_.StaticType | + Should not beNullOrEmpty + } + } + else + { + It 'does not declare a type' { + $parameter_.StaticType | + Should be 'System.Object' + } + It 'omit [AllowNull()]' { + $r = $parameter.Attributes | + ? {$_.TypeId -match 'AllowNull' } + $r | Should beNullOrEmpty + } + It 'does not use a validation script' { + $r = $parameter.Attributes | + ? {$_.TypeId.Name -eq 'ValidateScriptAttribute'} + $r | Should beNullOrEmpty + } + } + + } + } + foreach ( $parameter in ( + $function.Parameters.Values | + ? {$_.Name -notin (Get-CommonParameterNames)} | + ? {$_.Name -notin 'Mode','Ensure'} + ) + ) + { + Context "Resource Parameters: $($parameter.Name)" { + It 'has no default value' { + $r = $function.ScriptBlock.Ast.Body.ParamBlock.Parameters. + Where({$_.Name.VariablePath.UserPath -eq $($parameter.name)}). + DefaultValue + $r | Should beNullOrEmpty + } + } + } + } +} + +function Test-ResourcePlumbing +{ + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 1)] + $ResourceName, + + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 2)] + $ModuleName, + + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true, + Position = 3)] + $FunctionName + ) + # each of these operations should have already been tested outside this function + Import-Module $ModuleName + $modulePath = (Get-Module $ModuleName).NestedModules | + ? { $_.Name -eq $ResourceName } | + select -First 1 | + % Path + Import-Module $modulePath + $object = & (Get-Module $ResourceName).NewBoundScriptBlock( + [scriptblock]::Create("New-Object $ResourceName") + ) + $function = (Get-Module $ModuleName).ExportedFunctions.$FunctionName + + Describe "resource object $ResourceName in module $ModuleName to function $FunctionName" { + It 'the member variable and parameter names match' { + $memberNames = Get-Member -InputObject $object -MemberType Property | + % Name | + Sort + $parameterNames = $function.Parameters.get_keys() | + ? { $_ -notin (Get-CommonParameterNames) } | + ? { $_ -ne 'Mode' } | + Sort + $memberNames | Should be $parameterNames + } + Context 'the parameter/member correlation' { + $members = $object.GetType().GetMembers() | + ? {$_.MemberType -eq 'Property' } + foreach ( $member in $members ) + { + $name = $member.Name + + $parameter = $function.Parameters.$name + $parameter_ = $function.ScriptBlock.Ast.Body.ParamBlock.Parameters. + Where({$_.Name.VariablePath.UserPath -eq $name}) + + if ( $parameter_.StaticType.IsValueType ) + { + It "$name : the parameter type matches the member type" { + $memberType = $member.PropertyType + $parameterType = $parameter.ParameterType + + $parameterType | Should be $memberType + } + } + It "$name : the parameter mandatoriness matches the member mandatoriness" { + $parameterMandatory = [bool]($function.Parameters.$name.Attributes | + ? { $_.TypeId.Name -eq 'ParameterAttribute' } | + % Mandatory) + $memberMandatory = [bool]($object.GetType().DeclaredMembers | + ? {$_.Name -eq $name } | + % CustomAttributes | + ? {$_.AttributeType -match 'DscProperty' } | + % NamedArguments | + ? {$_.MemberName -eq 'Mandatory' } | + % TypedValue | + % Value) + $parameterMandatory | Should be $memberMandatory + } + } + } + + # compose the parameters + $parameters = @{} + foreach ( $variable in $object.GetType().GetProperties() ) + { + # if it's an enum, use the first value + if ( $variable.PropertyType.BaseType -eq [System.Enum] ) + { + $parameters.$($variable.Name) = [System.Enum]::GetValues($variable.PropertyType)[0] + } + # if it's a nullable enum, use the first value + elseif ( $variable.PropertyType.Name -eq 'Nullable`1' -and + $variable.PropertyType.GenericTypeArguments.BaseType -eq [System.Enum] ) + { + $parameters.$($variable.Name) = [System.Enum]::GetValues($variable.PropertyType.GenericTypeArguments[0])[0] + } + # if it's a nullable int, use 0 + elseif ( $variable.PropertyType.Name -eq 'Nullable`1' -and + $variable.PropertyType.BaseType -eq [System.ValueType] ) + { + $parameters.$($variable.Name) = 0 + } + # if there is a default value, use it + elseif ( $object.$($variable.Name) ) + { + $parameters.$($variable.Name) = $object.$($variable.Name) + } + else + { + switch ($variable.PropertyType ) + { + ([bool]) { $parameters.$($variable.Name) = $true } + ([string]) { $parameters.$($variable.Name) = $variable.Name.ToUpper() } + ([string[]]) { $parameters.$($variable.Name) = $variable.Name.ToUpper() } + ([int]) { + $parameters.$($variable.Name) = $variable.GetHashCode() + } + Default { + throw "Unhandled Type $($variable.GetType())" + } + } + } + } + + @{ + ResourceName = $ResourceName + ModuleName = $ModuleName + FunctionName = $FunctionName + Parameters = $parameters + } | + Export-CliXml TestDrive:/values.xml + + InModuleScope $ResourceName { + $v = Import-Clixml TestDrive:/values.xml + foreach ( $values in @( + @('Test',$true), + @('Set',$null) + )) + { + $mode,$retVal = $values + $expectedMode = $mode + Context "Invoke .$mode()" { + Mock $v.FunctionName { $retVal } -Verifiable + It 'passes return value' { + $o = New-Object $v.ResourceName -Property $v.Parameters + + $r = $o.$Mode() + + $r | Should be $retVal + } + It 'invokes command' { + $sb = [scriptblock]::Create( + (($v.Parameters.get_keys() | % { "`$$_ -eq '$($v.Parameters.$_)'" }) -join ' -and ') + ) + Assert-MockCalled $v.FunctionName 1 $sb + } + } + } + } + } + # Removing module causing problems with mocking later. Should test with removal again + # once PowerShell GH-2505 is fixed. + #Remove-Module $ResourceName +} + +function Get-CommonParameterNames +{ + [CmdletBinding()] + param() + $MyInvocation.MyCommand.Parameters.Keys +} \ No newline at end of file diff --git a/WindowsShell.psd1 b/WindowsShell.psd1 index ec2ae07..cfc27a5 100644 --- a/WindowsShell.psd1 +++ b/WindowsShell.psd1 @@ -2,7 +2,14 @@ # Script module or binary module file associated with this manifest. RootModule = 'WindowsShell.psm1' -NestedModules = 'ShellLibrary.psm1','ShellLibraryFolder.psm1','Shortcut.psm1' +NestedModules = 'Shortcut.psm1','ShellLibrary.psm1','ShellLibraryFolder.psm1' +ScriptsToProcess = @( + '.\dotNetTypes\ensure.ps1' + '.\dotNetTypes\mode.ps1' + '.\dotNetTypes\libraryTypeName.ps1' + '.\dotNetTypes\stockIconName.ps1' + '.\dotNetTypes\windowStyle.ps1' +) DscResourcesToExport = '*' @@ -26,4 +33,4 @@ PowerShellVersion = '5.0' # Name of the Windows PowerShell host required by this module # PowerShellHostName = '' -} \ No newline at end of file +} diff --git a/dotNetTypes/ensure.ps1 b/dotNetTypes/ensure.ps1 new file mode 100644 index 0000000..4dc93d1 --- /dev/null +++ b/dotNetTypes/ensure.ps1 @@ -0,0 +1 @@ +Add-Type "public enum Ensure { Present, Absent }" \ No newline at end of file diff --git a/dotNetTypes/libraryTypeName.ps1 b/dotNetTypes/libraryTypeName.ps1 new file mode 100644 index 0000000..916abea --- /dev/null +++ b/dotNetTypes/libraryTypeName.ps1 @@ -0,0 +1,10 @@ +Add-Type @' +public enum LibraryTypeName +{ + Generic, + Documents, + Music, + Pictures, + Videos +} +'@ \ No newline at end of file diff --git a/dotNetTypes/mode.ps1 b/dotNetTypes/mode.ps1 new file mode 100644 index 0000000..711f7da --- /dev/null +++ b/dotNetTypes/mode.ps1 @@ -0,0 +1 @@ +Add-Type "public enum Mode { Set, Test }" \ No newline at end of file diff --git a/dotNetTypes/stockIconName.ps1 b/dotNetTypes/stockIconName.ps1 new file mode 100644 index 0000000..10a7673 --- /dev/null +++ b/dotNetTypes/stockIconName.ps1 @@ -0,0 +1,16 @@ +Add-Type @' +public enum StockIconName +{ + DocumentNotAssociated, DocumentAssociated, Application, Folder, FolderOpen, Drive525, Drive35, DriveRemove, + DriveFixed, DriveNetwork, DriveNetworkDisabled, DriveCD, DriveRam, World, Server, Printer, MyNetwork, Find, Help, + Share, Link, SlowFile, Recycler, RecyclerFull, MediaCDAudio, Lock, AutoList, PrinterNet, ServerShare, PrinterFax, + PrinterFaxNet, PrinterFile, Stack, MediaSvcd, StuffedFolder, DriveUnknown, DriveDvd, MediaDvd, MediaDvdRam, + MediaDvdRW, MediaDvdR, MediaDvdRom, MediaCDAudioPlus, MediaCDRW, MediaCDR, MediaCDBurn, MediaBlankCD, MediaCDRom, + AudioFiles, ImageFiles, VideoFiles, MixedFiles, FolderBack, FolderFront, Shield, Warning, Info, Error, Key, + Software, Rename, Delete, MediaAudioDvd, MediaMovieDvd, MediaEnhancedCD, MediaEnhancedDvd, MediaHDDvd, + MediaBluRay, MediaVcd, MediaDvdPlusR, MediaDvdPlusRW, DesktopPC, MobilePC, Users, MediaSmartMedia, + MediaCompactFlash, DeviceCellPhone, DeviceCamera, DeviceVideoCamera, DeviceAudioPlayer, NetworkConnect, Internet, + ZipFile, Settings, DriveHDDVD, DriveBluRay, MediaHDDVDROM, MediaHDDVDR, MediaHDDVDRAM, MediaBluRayROM, + MediaBluRayR, MediaBluRayRE, ClusteredDisk +} +'@ \ No newline at end of file diff --git a/Functions/shortcutType.ps1 b/dotNetTypes/windowStyle.ps1 similarity index 86% rename from Functions/shortcutType.ps1 rename to dotNetTypes/windowStyle.ps1 index 7ddca21..9b07a96 100644 --- a/Functions/shortcutType.ps1 +++ b/dotNetTypes/windowStyle.ps1 @@ -10,9 +10,12 @@ intWindowStyle Description 3 Activates the window and displays it as a maximized window. 7 Minimizes the window and activates the next top-level window. #> -enum WindowStyle + +Add-Type @' +public enum WindowStyle { - Normal = 1 - Maximized = 3 + Normal = 1, + Maximized = 3, Minimized = 7 } +'@ \ No newline at end of file diff --git a/import.Tests.ps1 b/import.Tests.ps1 new file mode 100644 index 0000000..d28d249 --- /dev/null +++ b/import.Tests.ps1 @@ -0,0 +1,35 @@ +Describe 'import WindowsShell module' { + It 'import' { + Import-Module WindowsShell + } + It 'get' { + $r = Get-Module WindowsShell + $r | Should not beNullOrEmpty + } + It 'an instance from this folder was loaded' { + $r = (Get-Module WindowsShell).ModuleBase + $PSScriptRoot | Should be $r + } +} +Describe 'loading DSC Resources' { + foreach ( $name in 'Shortcut','ShellLibrary','ShellLibraryFolder' ) + { + Context $name { + It 'get' { + $r = Get-DscResource $name WindowsShell + $r | Should not beNullOrEmpty + } + It 'an instance from this folder was loaded' { + $r = Get-DscResource $name WindowsShell | + ? { $PSScriptRoot -eq $_.Module.ModuleBase } + $r | Should be $true + } + It 'the instance with the latest version is from this folder' { + $r = Get-DscResource $name WindowsShell | + Sort Version | + Select -Last 1 + $r.Module.ModuleBase | Should be $PSScriptRoot + } + } + } +} \ No newline at end of file diff --git a/resourcePlumbing.Tests.ps1 b/resourcePlumbing.Tests.ps1 new file mode 100644 index 0000000..bd3e804 --- /dev/null +++ b/resourcePlumbing.Tests.ps1 @@ -0,0 +1,15 @@ +Import-Module WindowsShell -Force + +. "$PSScriptRoot\TestFunctions\resourcePlumbing.ps1" + +foreach ( $resourceName in 'Shortcut','ShellLibrary','ShellLibraryFolder') +{ + $params = New-Object psobject -Property @{ + ModuleName = 'WindowsShell' + ResourceName = $resourceName + FunctionName = "Invoke-Process$resourceName" + } + $params | Test-ResourceObject + $params | Test-ProcessFunction + $params | Test-ResourcePlumbing +} \ No newline at end of file