From d4df7e52774285120e33ec96aa73c4487117c263 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Wed, 18 Jan 2023 16:12:38 +0100 Subject: [PATCH 01/11] Initial commit --- CHANGELOG.md | 1 + source/Classes/002.FileSystemObject.ps1 | 409 ++++++++++++ source/FileSystemDsc.psd1 | 36 +- ...DSC_FileSystemObject.Integration.Tests.ps1 | 625 ++++++++++++++++++ .../DSC_FileSystemObject.config.ps1 | 300 +++++++++ 5 files changed, 1349 insertions(+), 22 deletions(-) create mode 100644 source/Classes/002.FileSystemObject.ps1 create mode 100644 tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_FileSystemObject.config.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c00e17..c64a684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added wiki generation and publish to GitHub repository wiki. - Added recommended VS Code extensions. - Added settings for VS Code extension _Pester Test Adapter_. + - New File resource added to enable cross-platform file operations. ### Changed diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 new file mode 100644 index 0000000..33f1d65 --- /dev/null +++ b/source/Classes/002.FileSystemObject.ps1 @@ -0,0 +1,409 @@ + +enum ensure +{ + present + absent +} + +enum objectType +{ + file + directory + symboliclink +} +enum linkBehavior +{ + follow + manage +} +enum checksumType +{ + md5 + mtime + ctime +} + +enum encoding +{ + ASCII + Latin1 + UTF7 + UTF8 + UTF32 + BigEndianUnicode + Default + Unicode +} + +class FileSystemDscReason +{ + [DscProperty()] + [System.String] + $Code + + [DscProperty()] + [System.String] + $Phrase +} + +<# + .SYNOPSIS + The File resource enables file system operations on Linux and Windows. + With regards to parameters and globbing, it behaves like the Item and Content + cmdlets. + + .PARAMETER DestinationPath + The path to create/copy to. + + .PARAMETER SourcePath + If data should be copied, the source path to copy from. + .PARAMETER Ensure + Indicates if destination should be created or removed. Values: Absent, Present. Default: Present. + + .PARAMETER Type + The type of the object to create. Values: file, directory, symboliclink. Default: directory + + .PARAMETER Contents + The file contents. Unused if type is directory + + .PARAMETER Checksum + The type of checksum to use for copy operations. Values: md5, ctime, mtime. Default: md5 + + .PARAMETER Recurse + Indicates that recurse should be used if data is copied. + + .PARAMETER Force + Indicates that folder structures should be created and existing files overwritten + + .PARAMETER Links + Link behavior, currently not implemented. Values: follow, manage. Default: follow + + .PARAMETER Group + Linux group name for chown, currently not implemented. + + .PARAMETER Mode + Linux mode for chmod, currently not implemented. + + .PARAMETER Owner + Linux owner name for chown, currently not implemented. + + .PARAMETER Encoding + File encoding, used with Contents. Values: ASCII, Latin1, UTF7, UTF8, UTF32, BigEndianUnicode, Default, Unicode. Default: Default + + .PARAMETER IgnoreTrailingWhitespace + Indicates that trailing whitespace should be ignored when comparing file contents. +#> +[DscResource()] +class FileSystemObject +{ + [DscProperty(Key)] + [string] + $DestinationPath + + [DscProperty()] + [string] + $SourcePath + + [DscProperty()] + [ensure] + $Ensure = [ensure]::present + + [DscProperty()] + [objectType] + $Type = [objectType]::directory + + [DscProperty()] + [string] + $Contents + + [DscProperty()] + [checksumType] + $Checksum = [checksumType]::md5 + + [DscProperty()] + [bool] + $Recurse = $false + + [DscProperty()] + [bool] + $Force = $false + + [DscProperty()] + [linkBehavior] + $Links = [linkBehavior]::follow + + [DscProperty()] + [string] + $Group + + [DscProperty()] + [string] + $Mode + + [DscProperty()] + [string] + $Owner + + [DscProperty(NotConfigurable)] + [datetime] + $CreatedDate + + [DscProperty(NotConfigurable)] + [datetime] + $ModifiedDate + + [DscProperty()] + [encoding] + $Encoding = 'Default' + + [DscProperty()] + [bool] + $IgnoreTrailingWhitespace + + [DscProperty(NotConfigurable)] + [FileSystemDscReason[]] + $Reasons + + [FileSystemObject] Get () + { + $returnable = @{ + DestinationPath = $this.DestinationPath + SourcePath = $this.SourcePath + Ensure = $this.Ensure + Type = $this.Type + Contents = '' + Checksum = $this.Checksum + Recurse = $this.Recurse + Force = $this.Force + Links = $this.Links + Encoding = $this.Encoding + Group = '' + Mode = '' + Owner = '' + IgnoreTrailingWhitespace = $this.IgnoreTrailingWhitespace + CreatedDate = [datetime]::new(0) + ModifiedDate = [datetime]::new(0) + Reasons = @() + } + + $object = Get-Item -ErrorAction SilentlyContinue -Path $this.DestinationPath -Force + if ($null -eq $object -and $this.Ensure -eq [ensure]::present) + { + Write-Verbose -Message "Object $($this.DestinationPath) does not exist, but Ensure is set to 'Present'" + $returnable.Reasons += @{ + Code = "File:File:ObjectMissingWhenItShouldExist" + Phrase = "Object $($this.DestinationPath) does not exist, but Ensure is set to 'Present'" + } + return [FileSystemObject]$returnable + } + + if ($null -ne $object -and $this.Ensure -eq [ensure]::absent) + { + Write-Verbose -Message "Object $($this.DestinationPath) exists, but Ensure is set to 'Absent'" + $returnable.Reasons += @{ + Code = "File:File:ObjectExistsWhenItShouldNot" + Phrase = "Object $($this.DestinationPath) exists, but Ensure is set to 'Absent'" + } + return [FileSystemObject]$returnable + } + + if ($object.Count -eq 1 -and ($object.Attributes -band 'ReparsePoint') -eq 'ReparsePoint') + { + $returnable.Type = 'SymbolicLink' + } + elseif ($object.Count -eq 1 -and ($object.Attributes -band 'Directory') -eq 'Directory') + { + $returnable.Type = 'Directory' + } + elseif ($object.Count -eq 1) + { + $returnable.Type = 'File' + } + + if ($returnable.Type -ne $this.Type) + { + $returnable.Reasons += @{ + Code = "File:File:TypeMismatch" + Phrase = "Type of $($object.FullName) has type '$($returnable.Type)', should be '$($this.Type)'" + } + } + + $returnable.DestinationPath = $object.FullName + if ([string]::IsNullOrWhiteSpace($this.SourcePath) -and $object -and $this.Type -eq [objectType]::file) + { + $returnable.Contents = Get-Content -Raw -Path $object.FullName -Encoding $this.Encoding.ToString() + } + + if (-not $this.Ensure -eq 'Absent' -and -not [string]::IsNullOrWhiteSpace($returnable.Contents) -and $this.IgnoreTrailingWhitespace) + { + $returnable.Contents = $returnable.Contents.Trim() + } + + if ($this.Contents -and $returnable.Contents -ne $this.Contents) + { + $returnable.Reasons += @{ + Code = "File:File:ContentMismatch" + Phrase = "Content of $($object.FullName) different from parameter Contents" + } + } + + if ($object.Count -eq 1) + { + $returnable.CreatedDate = $object.CreationTime + $returnable.ModifiedDate = $object.LastWriteTime + $returnable.Owner = $object.User + $returnable.Mode = $object.Mode + $returnable.Group = $object.Group + } + + if (-not [string]::IsNullOrWhiteSpace($this.SourcePath)) + { + if (-not $this.Recurse -and $this.Type -eq [objectType]::directory) + { + Write-Verbose -Message "Directory is copied without Recurse parameter. Skipping file checksum" + return [FileSystemObject]$returnable + } + + $destination = if (-not $this.Recurse -and $this.SourcePath -notmatch '\*\?\[\]') + { + Join-Path $this.DestinationPath (Split-Path $this.SourcePath -Leaf) + } + else + { + $this.DestinationPath + } + + $currHash = $this.CompareHash($destination, $this.SourcePath, $this.Checksum, $this.Recurse) + + if ($currHash.Count -gt 0) + { + Write-Verbose -Message "Hashes of files in $($this.DestinationPath) (comparison path used: $destination) different from hashes in $($this.SourcePath)" + $returnable.Reasons += @{ + Code = "File:File:HashMismatch" + Phrase = "Hashes of files in $($this.DestinationPath) different from hashes in $($this.SourcePath)" + } + } + } + return [FileSystemObject]$returnable + } + + [void] Set() + { + if ($this.Ensure -eq 'Absent') + { + Write-Verbose -Message "Removing $($this.DestinationPath) with Recurse and Force" + Remove-Item -Recurse -Force -Path $this.DestinationPath + return + } + + if ($this.Type -in [objectType]::file, [objectType]::directory -and [string]::IsNullOrWhiteSpace($this.SourcePath)) + { + Write-Verbose -Message "Creating new $($this.Type) $($this.DestinationPath), Force" + $param = @{ + ItemType = $this.Type + Path = $this.DestinationPath + } + if ($this.Force) + { + $param['Force'] = $true + } + $null = New-Item @param + } + + if ($this.Type -eq [objectType]::SymbolicLink) + { + Write-Verbose -Message "Creating new symbolic link $($this.DestinationPath) --> $($this.SourcePath)" + New-Item -ItemType SymbolicLink -Path $this.DestinationPath -Value $this.SourcePath + return + } + + if ($this.Contents) + { + Write-Verbose -Message "Setting content of $($this.DestinationPath) using $($this.Encoding)" + $this.Contents | Set-Content -Path $this.DestinationPath -Force -Encoding $this.Encoding.ToString() -NoNewline + } + + if ($this.SourcePath -and ($this.SourcePath -match '\*|\?\[\]') -and -not (Test-Path -Path $this.DestinationPath)) + { + Write-Verbose -Message "Creating destination directory for wildcard copy $($this.DestinationPath)" + $null = New-Item -ItemType Directory -Path $this.DestinationPath + } + + if ($this.SourcePath) + { + Write-Verbose -Message "Copying from $($this.SourcePath) to $($This.DestinationPath), Recurse is $($this.Recurse), Using the Force: $($this.Force)" + $copyParam = @{ + Path = $this.SourcePath + Destination = $this.DestinationPath + } + if ($this.Recurse) + { + $copyParam['Recurse'] = $this.Recurse + } + if ($this.Force) + { + $copyParam['Force'] = $this.Force + } + Copy-Item @copyParam + } + } + + [bool] Test() + { + $currentState = $this.Get() + + return ($currentState.Reasons.Count -eq 0) + } + + [System.IO.FileInfo[]] CompareHash([string]$Path, [string]$ReferencePath, [checksumType]$Type = 'md5', [bool]$Recurse) + { + [object[]]$sourceHashes = $this.GetHash($ReferencePath, $Type, $Recurse) + [object[]]$hashes = $this.GetHash($Path, $Type, $Recurse) + + if ($hashes.Count -eq 0) + { + return [System.IO.FileInfo[]]$sourceHashes.Path + } + + $comparison = Compare-Object -ReferenceObject $sourceHashes -DifferenceObject $hashes -Property Hash -PassThru | Where-Object SideIndicator -eq '<=' + return [System.IO.FileInfo[]]$comparison.Path + } + + # Return type unclear and either Microsoft.PowerShell.Commands.FileHashInfo or PSCustomObject + # Might be better to create a custom class for this + [object[]] GetHash([string]$Path, [checksumType]$Type, [bool]$Recurse) + { + $hashStrings = if ($Type -eq 'md5') + { + Get-ChildItem -Recurse:$Recurse -Path $Path -Force -File | Get-FileHash -Algorithm md5 + } + else + { + $propz = @( + @{ + Name = 'Path' + Expression = { $_.FullName } + } + @{ + Name = 'Algorithm' + Expression = { $Type } + } + @{ + Name = 'Hash' + Expression = { if ($Type -eq 'ctime') + { + $_.CreationTime + } + else + { + $_.LastWriteTime + } + } + } + ) + Get-ChildItem -Recurse:$Recurse -Path $Path -Force -File | Select-Object -Property $propz + } + + return $hashStrings + } +} diff --git a/source/FileSystemDsc.psd1 b/source/FileSystemDsc.psd1 index 1f9884e..f83766e 100644 --- a/source/FileSystemDsc.psd1 +++ b/source/FileSystemDsc.psd1 @@ -1,48 +1,40 @@ @{ + RootModule = 'FileSystemDsc.psm1' # Version number of this module. - moduleVersion = '0.0.1' + ModuleVersion = '0.0.1' # ID used to uniquely identify this module - GUID = '86a20a80-3bcd-477e-9b90-ec8d52fbe415' + GUID = '86a20a80-3bcd-477e-9b90-ec8d52fbe415' + + CompatiblePSEditions = @('Core', 'Desktop') # Author of this module - Author = 'DSC Community' + Author = 'DSC Community' # Company or vendor of this module - CompanyName = 'DSC Community' + CompanyName = 'DSC Community' # Copyright statement for this module - Copyright = 'Copyright the DSC Community contributors. All rights reserved.' + Copyright = 'Copyright the DSC Community contributors. All rights reserved.' # Description of the functionality provided by this module - Description = 'This module contains DSC resources for managing file systems.' + Description = 'This module contains DSC resources for managing file systems.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.1' + PowerShellVersion = '5.1' # Minimum version of the common language runtime (CLR) required by this module - CLRVersion = '4.0' - - # Functions to export from this module - FunctionsToExport = @() - - # Cmdlets to export from this module - CmdletsToExport = @() - - # Variables to export from this module - VariablesToExport = @() - - # Aliases to export from this module - AliasesToExport = @() + CLRVersion = '4.0' DscResourcesToExport = @( 'FileSystemAccessRule' + 'File' ) - RequiredAssemblies = @() + RequiredAssemblies = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ + PrivateData = @{ PSData = @{ # Set to a prerelease string value if the release should be a prerelease. Prerelease = '' diff --git a/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 b/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 new file mode 100644 index 0000000..21a7a4d --- /dev/null +++ b/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 @@ -0,0 +1,625 @@ +BeforeDiscovery { + try + { + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' + } + + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscResourceFriendlyName = 'FileSystemObject' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + $script:temproot = Join-Path -Path ([io.Path]::GetTempPath()) -ChildPath DscFileIntTest + $script:tempdir = Join-Path -Path $temproot -ChildPath Source + $script:tempDirDestination = Join-Path -Path $temproot -ChildPath Destination +} + +BeforeAll { + # Need to define the variables here which will be used in Pester Run. + $script:dscModuleName = 'FileSystemDsc' + $script:dscResourceFriendlyName = 'FileSystemObject' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' + + # This helper function should be removed when it is merged into DscResource.Test + function Wait-ForIdleLcm + { + [CmdletBinding()] + param () + + while ((Get-DscLocalConfigurationManager).LCMState -ne 'Idle') + { + Write-Verbose -Message 'Waiting for the LCM to become idle' + + Start-Sleep -Seconds 2 + } + } + + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile +} + +AfterAll { + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Describe "_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_EmptyDir_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It "Should be able to find $($script:tempdir )" { + Test-Path -Path $script:tempdir -PathType Container | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_EmptyFile_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should find the newly created file' { + Test-Path -Path (Join-Path -Path $script:tempdir -ChildPath emptyfile) -PathType Leaf | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CreateFile_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should find the newly created file and validate its contents' { + Test-Path -Path (Join-Path -Path $script:tempdir -ChildPath contentfile) -PathType Leaf | Should -BeTrue + Get-Content -Path (Join-Path -Path $script:tempdir -ChildPath contentfile) | Should -Be 'It works' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyFile_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied a single file' { + Test-Path -Path (Join-Path -Path $script:tempDirDestination -ChildPath copiedfile) -PathType Leaf | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyFileWildcard_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied multiple files' { + (Get-ChildItem -Path (Join-Path -Path $script:tempDirDestination -ChildPath "copydestfilewc")).Count | Should -Be 2 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyDir_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied single dir' { + Test-Path -Path (Join-Path -Path $tempDirDestination -ChildPath "copydestfilewc") | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyDirWildcard_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied single dir with wildcard pattern' { + Test-Path -Path (Join-Path -Path $tempDirDestination -ChildPath "copydestfilewc\contentfile") | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyDirRecurse_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied single dir recursive' { + Test-Path -Path (Join-Path -Path $tempDirDestination -ChildPath 'this\is\recursive') | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CopyDirRecurseWildcard_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have copied single dir recursive wildcard' { + Test-Path -Path (Join-Path -Path $tempDirDestination -ChildPath "copydestdirrec\recursive\thing") | Should -BeTrue + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveFile_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have removed single file' { + Test-Path -Path (Join-Path -Path $tempdir -ChildPath "emptyfile") | Should -BeFalse + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveFileWildcard_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have removed single file' { + (Get-ChildItem -Path (Join-Path -Path $tempdir -ChildPath "copydestfilewc")).Count | Should -Be 0 + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveDirRecurse_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have removed single folder' { + Test-Path -Path $tempdir | Should -BeFalse + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } +} diff --git a/tests/Integration/DSC_FileSystemObject.config.ps1 b/tests/Integration/DSC_FileSystemObject.config.ps1 new file mode 100644 index 0000000..b840856 --- /dev/null +++ b/tests/Integration/DSC_FileSystemObject.config.ps1 @@ -0,0 +1,300 @@ +$temproot = Join-Path -Path ([io.Path]::GetTempPath()) -ChildPath DscFileIntTest +$tempdir = Join-Path -Path $temproot -ChildPath Source +$tempDirDestination = Join-Path -Path $temproot -ChildPath Destination +$null = New-Item -ItemType Directory -Path $tempDirDestination -ErrorAction SilentlyContinue + +<# + .SYNOPSIS + Create empty directory +#> +configuration DSC_FileSystemObject_EmptyDir +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject EmptyDir + { + DestinationPath = $tempdir + Type = 'directory' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Create an empty file + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_EmptyFile +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject EmptyFile + { + DestinationPath = Join-Path -Path $tempdir -ChildPath "emptyfile" + Type = 'file' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Create a file with content and encoding utf8 + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_CreateFile +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject FileContent + { + DestinationPath = Join-Path -Path $tempdir -ChildPath "contentfile" + Type = 'file' + Ensure = 'present' + Contents = 'It works' + Encoding = 'utf8' + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy single file + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_CopyFile +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject CopyFile + { + DestinationPath = Join-Path -Path $tempDirDestination -ChildPath "copiedfile" + SourcePath = Join-Path -Path $tempdir -ChildPath "contentfile" + Type = 'file' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy several files with wildcard + + .NOTES + This requires that the temporary dir was created in the very first test + and that files were created in previous tests +#> +configuration DSC_FileSystemObject_CopyFileWildcard +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject CopyFile + { + DestinationPath = Join-Path -Path $tempDirDestination -ChildPath "copydestfilewc" + SourcePath = Join-Path -Path $tempdir -ChildPath "*file" + Type = 'file' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy single directory + + .NOTES + This requires that the temporary dir was created in the very first test + and files were created in previous tests +#> +configuration DSC_FileSystemObject_CopyDir +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject CopyDir + { + DestinationPath = $tempDirDestination + SourcePath = $tempdir + Type = 'directory' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy directories using a wildcard pattern + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_CopyDirWildcard +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject CopyDirWc + { + DestinationPath = $tempDirDestination + SourcePath = Join-Path -Path $tempdir -ChildPath "*" + Type = 'directory' + Ensure = 'present' + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy single directory recursively + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_CopyDirRecurse +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject CopyDirRec + { + DestinationPath = $tempDirDestination + SourcePath = $tempdir + Type = 'directory' + Ensure = 'present' + Recurse = $true + Force = $true + } + } +} + +<# + .SYNOPSIS + Copy directory recursive using wildcard pattern + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_CopyDirRecurseWildcard +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject SourceObject + { + DestinationPath = Join-Path -Path $tempDir -ChildPath 'this\is\recursive' + Type = 'file' + Ensure = 'present' + Recurse = $true + Force = $true + } + + FileSystemObject CopyDirRecWc + { + DestinationPath = $tempDirDestination + SourcePath = Join-Path -Path $tempdir -ChildPath "*" + Type = 'directory' + Ensure = 'present' + Recurse = $true + Force = $true + DependsOn = '[FileSystemObject]SourceObject' + } + } +} + +<# + .SYNOPSIS + Remove a single file + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_RemoveFile +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject EmptyFile + { + DestinationPath = Join-Path -Path $tempdir -ChildPath "emptyfile" + Type = 'file' + Ensure = 'absent' + Force = $true + } + } +} + +<# + .SYNOPSIS + Remove files using a wildcard pattern + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_RemoveFileWildcard +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject RemoveFileWc + { + DestinationPath = Join-Path -Path $tempdir -ChildPath "copydestfilewc\*" + Type = 'file' + Force = $true + Ensure = 'absent' + } + } +} + +<# + .SYNOPSIS + Remove the temporary directory, thereby cleaning up all test files + + .NOTES + This requires that the temporary dir was created in the very first test +#> +configuration DSC_FileSystemObject_RemoveDirRecurse +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject RemoveDirRecurse + { + DestinationPath = $tempRoot + Type = 'file' + Ensure = 'absent' + Force = $true + Recurse = $true + } + } +} From abdaa404bf9b45f370f36fb55358ec3f681563b4 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Wed, 18 Jan 2023 16:28:04 +0100 Subject: [PATCH 02/11] Sample added --- source/Classes/002.FileSystemObject.ps1 | 9 ++++++ ...temObject_CreateFileWithContent_Config.ps1 | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 index 33f1d65..1f682cf 100644 --- a/source/Classes/002.FileSystemObject.ps1 +++ b/source/Classes/002.FileSystemObject.ps1 @@ -186,6 +186,15 @@ class FileSystemObject Reasons = @() } + if ($this.Type -eq [objectType]::directory -and -not [string]::IsNullOrWhiteSpace($this.Contents)) + { + Write-Verbose -Message "Type is directory, yet parameter Contents was used." + $returnable.Reasons += @{ + Code = "File:File:ParameterMismatch" + Phrase = "Type is directory, yet parameter Contents was used." + } + } + $object = Get-Item -ErrorAction SilentlyContinue -Path $this.DestinationPath -Force if ($null -eq $object -and $this.Ensure -eq [ensure]::present) { diff --git a/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 new file mode 100644 index 0000000..7266d0e --- /dev/null +++ b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 @@ -0,0 +1,31 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID e479ea7f-0427-40a5-96ab-17215511b05f +.AUTHOR DSC Community +.COMPANYNAME DSC Community +.COPYRIGHT DSC Community contributors. All rights reserved. +.TAGS DSCConfiguration +.LICENSEURI https://github.com/dsccommunity/FileSystemDsc/blob/main/LICENSE +.PROJECTURI https://github.com/dsccommunity/FileSystemDsc +.ICONURI https://dsccommunity.org/images/DSC_Logo_300p.png +.RELEASENOTES +First release. +#> + +#Requires -Module FileSystemDsc + +Configuration FileSystemObject_CreateFileWithContent_Config +{ + Import-DscResource -ModuleName FileSystemDsc + + node localhost + { + FileSystemObject MyFile + { + DestinationPath = 'C:\inetpub\wwwroot\index.html' + Contents = 'My PageDSC is the best' + Type = 'file' + Ensure = 'present' + } + } +} From 6f731b5a74a895ed242379702597fcdf1e86f134 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Wed, 18 Jan 2023 19:34:03 +0100 Subject: [PATCH 03/11] add empty strings file --- source/en-US/FileSystemDsc.strings.psd1 | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 source/en-US/FileSystemDsc.strings.psd1 diff --git a/source/en-US/FileSystemDsc.strings.psd1 b/source/en-US/FileSystemDsc.strings.psd1 new file mode 100644 index 0000000..b0c4547 --- /dev/null +++ b/source/en-US/FileSystemDsc.strings.psd1 @@ -0,0 +1,9 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource FileSystemDsc module. This file should only contain + localized strings for private and public functions. +#> + +ConvertFrom-StringData @' +'@ From 5a4f764e900a1b432c49211f7f49ec61bb01c092 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Wed, 18 Jan 2023 19:57:32 +0100 Subject: [PATCH 04/11] Update ScriptFileInfo --- .../1-FileSystemObject_CreateFileWithContent_Config.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 index 7266d0e..33c1352 100644 --- a/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 +++ b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 @@ -14,6 +14,12 @@ First release. #Requires -Module FileSystemDsc +<# + +.DESCRIPTION + Sample to create a file with contents. + +#> Configuration FileSystemObject_CreateFileWithContent_Config { Import-DscResource -ModuleName FileSystemDsc From bb021cf5a2eae8b8bef45a323e6ce946d2fe2b98 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Thu, 19 Jan 2023 08:12:35 +0100 Subject: [PATCH 05/11] update sample code --- .../1-FileSystemObject_CreateFileWithContent_Config.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 index 33c1352..04d39fb 100644 --- a/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 +++ b/source/Examples/Resources/FileSystemObject/1-FileSystemObject_CreateFileWithContent_Config.ps1 @@ -1,6 +1,6 @@ <#PSScriptInfo .VERSION 1.0.0 -.GUID e479ea7f-0427-40a5-96ab-17215511b05f +.GUID e479ea7f-abcd-40a5-96ab-17215511b05f .AUTHOR DSC Community .COMPANYNAME DSC Community .COPYRIGHT DSC Community contributors. All rights reserved. From d909af7a667a7cf14dc21bddd02351c5cf108f31 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Thu, 19 Jan 2023 09:03:18 +0100 Subject: [PATCH 06/11] Move enums to individual files --- source/Classes/002.FileSystemObject.ps1 | 38 +------------------------ source/enum/checksumType.ps1 | 6 ++++ source/enum/encoding.ps1 | 11 +++++++ source/enum/ensure.ps1 | 6 ++++ source/enum/linkBehavior.ps1 | 5 ++++ source/enum/objectType.ps1 | 6 ++++ 6 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 source/enum/checksumType.ps1 create mode 100644 source/enum/encoding.ps1 create mode 100644 source/enum/ensure.ps1 create mode 100644 source/enum/linkBehavior.ps1 create mode 100644 source/enum/objectType.ps1 diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 index 1f682cf..92bd2c9 100644 --- a/source/Classes/002.FileSystemObject.ps1 +++ b/source/Classes/002.FileSystemObject.ps1 @@ -1,40 +1,3 @@ - -enum ensure -{ - present - absent -} - -enum objectType -{ - file - directory - symboliclink -} -enum linkBehavior -{ - follow - manage -} -enum checksumType -{ - md5 - mtime - ctime -} - -enum encoding -{ - ASCII - Latin1 - UTF7 - UTF8 - UTF32 - BigEndianUnicode - Default - Unicode -} - class FileSystemDscReason { [DscProperty()] @@ -193,6 +156,7 @@ class FileSystemObject Code = "File:File:ParameterMismatch" Phrase = "Type is directory, yet parameter Contents was used." } + return [FileSystemObject]$returnable } $object = Get-Item -ErrorAction SilentlyContinue -Path $this.DestinationPath -Force diff --git a/source/enum/checksumType.ps1 b/source/enum/checksumType.ps1 new file mode 100644 index 0000000..902265c --- /dev/null +++ b/source/enum/checksumType.ps1 @@ -0,0 +1,6 @@ +enum checksumType +{ + md5 + mtime + ctime +} diff --git a/source/enum/encoding.ps1 b/source/enum/encoding.ps1 new file mode 100644 index 0000000..16365ee --- /dev/null +++ b/source/enum/encoding.ps1 @@ -0,0 +1,11 @@ +enum encoding +{ + ASCII + Latin1 + UTF7 + UTF8 + UTF32 + BigEndianUnicode + Default + Unicode +} diff --git a/source/enum/ensure.ps1 b/source/enum/ensure.ps1 new file mode 100644 index 0000000..d88b173 --- /dev/null +++ b/source/enum/ensure.ps1 @@ -0,0 +1,6 @@ +enum ensure +{ + present + absent +} + diff --git a/source/enum/linkBehavior.ps1 b/source/enum/linkBehavior.ps1 new file mode 100644 index 0000000..2cd8327 --- /dev/null +++ b/source/enum/linkBehavior.ps1 @@ -0,0 +1,5 @@ +enum linkBehavior +{ + follow + manage +} diff --git a/source/enum/objectType.ps1 b/source/enum/objectType.ps1 new file mode 100644 index 0000000..fcbba0c --- /dev/null +++ b/source/enum/objectType.ps1 @@ -0,0 +1,6 @@ +enum objectType +{ + file + directory + symboliclink +} From e4f8fd32b79c0ad70dd87a2cec8b886d3f9c3941 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Thu, 19 Jan 2023 10:14:10 +0100 Subject: [PATCH 07/11] Add rudimentary tests --- source/Classes/002.FileSystemObject.ps1 | 6 +- source/enum/checksumType.ps1 | 4 +- tests/Unit/DSC_FileSystemObject.Tests.ps1 | 166 ++++++++++++++++++++++ 3 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/DSC_FileSystemObject.Tests.ps1 diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 index 92bd2c9..3c17942 100644 --- a/source/Classes/002.FileSystemObject.ps1 +++ b/source/Classes/002.FileSystemObject.ps1 @@ -30,7 +30,7 @@ class FileSystemDscReason The file contents. Unused if type is directory .PARAMETER Checksum - The type of checksum to use for copy operations. Values: md5, ctime, mtime. Default: md5 + The type of checksum to use for copy operations. Values: md5, CreationTime, LastModifiedTime. Default: md5 .PARAMETER Recurse Indicates that recurse should be used if data is copied. @@ -212,7 +212,7 @@ class FileSystemObject $returnable.Contents = $returnable.Contents.Trim() } - if ($this.Contents -and $returnable.Contents -ne $this.Contents) + if (-not [string]::IsNullOrWhiteSpace($this.Contents) -and $returnable.Contents -ne $this.Contents) { $returnable.Reasons += @{ Code = "File:File:ContentMismatch" @@ -363,7 +363,7 @@ class FileSystemObject } @{ Name = 'Hash' - Expression = { if ($Type -eq 'ctime') + Expression = { if ($Type -eq 'CreationTime') { $_.CreationTime } diff --git a/source/enum/checksumType.ps1 b/source/enum/checksumType.ps1 index 902265c..90e2e89 100644 --- a/source/enum/checksumType.ps1 +++ b/source/enum/checksumType.ps1 @@ -1,6 +1,6 @@ enum checksumType { md5 - mtime - ctime + LastModifiedTime + CreationTime } diff --git a/tests/Unit/DSC_FileSystemObject.Tests.ps1 b/tests/Unit/DSC_FileSystemObject.Tests.ps1 new file mode 100644 index 0000000..9bb8964 --- /dev/null +++ b/tests/Unit/DSC_FileSystemObject.Tests.ps1 @@ -0,0 +1,166 @@ +<# + .SYNOPSIS + Unit test for DSC_FileSystemObject DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'FileSystemDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + # Remove module common test helper. + Get-Module -Name 'CommonTestHelper' -All | Remove-Module -Force +} + +Describe 'FileSystemObject' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [FileSystemObject]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [FileSystemObject]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [FileSystemObject]::new() + $instance.GetType().Name | Should -Be 'FileSystemObject' + } + } + } +} + +Describe 'FileSystemObject\Get()' -Tag 'Get' { + Context 'When the system is in the desired state'-Skip { + + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockFileSystemObjectInstanceDir = [FileSystemObject] @{ + Ensure = 'present' + Type = 'directory' + DestinationPath = 'C:\MadeUpDir' + } + $script:mockFileSystemObjectInstanceDirCopyRecurse = [FileSystemObject] @{ + Ensure = 'present' + Type = 'directory' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource' + Recurse = $true + Force = $true + } + $script:mockFileSystemObjectInstanceDirCopyRecurseWildcard = [FileSystemObject] @{ + Ensure = 'present' + Type = 'directory' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource\*' + } + $script:mockFileSystemObjectInstanceFile = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + DestinationPath = 'C:\MadeUpDir\madeupfile' + } + $script:mockFileSystemObjectInstanceFileContent = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + Contents = 'Ladies and Gentlemen: The Content!' + DestinationPath = 'C:\MadeUpDir\madeupfile' + } + $script:mockFileSystemObjectInstanceFileCopyDefault = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource\madeupfile' + } + $script:mockFileSystemObjectInstanceFileCopyCreation = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource\madeupfile' + Checksum = 'CreationTime' + } + $script:mockFileSystemObjectInstanceFileCopyModified = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource\madeupfile' + Checksum = 'LastModifiedTime' + } + $script:mockFileSystemObjectInstanceFileCopyWildcard = [FileSystemObject] @{ + Ensure = 'present' + Type = 'file' + DestinationPath = 'C:\MadeUpDir' + SourcePath = 'D:\MadeUpSource\*file*' + } + + # Empty hash function results, since system is in desired state + # GetHash should not be called at any rate, as CompareHash is "mocked" + foreach ($variable in (Get-Variable -Scope Script -Name mockFileSystemObjectInstance*)) + { + $variable.Value | Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetHash' -Value { return } + $variable.Value | Add-Member -Force -MemberType 'ScriptMethod' -Name 'CompareHash' -Value { return } + } + } + } + } +} + +Describe 'FileSystemObject\Set()' -Tag 'Set' -Skip { + +} + +Describe 'FileSystemObject\Test()' -Tag 'Test' -Skip { + +} + +Describe 'FileSystemObject\GetHash()' -Tag 'GetHash' -Skip { + +} + +Describe 'FileSystemObject\CompareHash()' -Tag 'CompareHash' -Skip { + +} From 93265b1921baab5e4af171903972df07421c27ef Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Thu, 19 Jan 2023 10:21:54 +0100 Subject: [PATCH 08/11] FileSystemObject Integration tests updated --- .../DSC_FileSystemObject.config.ps1 | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Integration/DSC_FileSystemObject.config.ps1 b/tests/Integration/DSC_FileSystemObject.config.ps1 index b840856..b9d190e 100644 --- a/tests/Integration/DSC_FileSystemObject.config.ps1 +++ b/tests/Integration/DSC_FileSystemObject.config.ps1 @@ -7,7 +7,7 @@ $null = New-Item -ItemType Directory -Path $tempDirDestination -ErrorAction Sile .SYNOPSIS Create empty directory #> -configuration DSC_FileSystemObject_EmptyDir +configuration DSC_FileSystemObject_EmptyDir_Config { Import-DscResource -ModuleName FileSystemDsc @@ -30,7 +30,7 @@ configuration DSC_FileSystemObject_EmptyDir .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_EmptyFile +configuration DSC_FileSystemObject_EmptyFile_Config { Import-DscResource -ModuleName FileSystemDsc @@ -53,7 +53,7 @@ configuration DSC_FileSystemObject_EmptyFile .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_CreateFile +configuration DSC_FileSystemObject_CreateFile_Config { Import-DscResource -ModuleName FileSystemDsc @@ -78,7 +78,7 @@ configuration DSC_FileSystemObject_CreateFile .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_CopyFile +configuration DSC_FileSystemObject_CopyFile_Config { Import-DscResource -ModuleName FileSystemDsc @@ -103,7 +103,7 @@ configuration DSC_FileSystemObject_CopyFile This requires that the temporary dir was created in the very first test and that files were created in previous tests #> -configuration DSC_FileSystemObject_CopyFileWildcard +configuration DSC_FileSystemObject_CopyFileWildcard_Config { Import-DscResource -ModuleName FileSystemDsc @@ -128,7 +128,7 @@ configuration DSC_FileSystemObject_CopyFileWildcard This requires that the temporary dir was created in the very first test and files were created in previous tests #> -configuration DSC_FileSystemObject_CopyDir +configuration DSC_FileSystemObject_CopyDir_Config { Import-DscResource -ModuleName FileSystemDsc @@ -152,7 +152,7 @@ configuration DSC_FileSystemObject_CopyDir .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_CopyDirWildcard +configuration DSC_FileSystemObject_CopyDirWildcard_Config { Import-DscResource -ModuleName FileSystemDsc @@ -176,7 +176,7 @@ configuration DSC_FileSystemObject_CopyDirWildcard .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_CopyDirRecurse +configuration DSC_FileSystemObject_CopyDirRecurse_Config { Import-DscResource -ModuleName FileSystemDsc @@ -201,7 +201,7 @@ configuration DSC_FileSystemObject_CopyDirRecurse .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_CopyDirRecurseWildcard +configuration DSC_FileSystemObject_CopyDirRecurseWildcard_Config { Import-DscResource -ModuleName FileSystemDsc @@ -236,7 +236,7 @@ configuration DSC_FileSystemObject_CopyDirRecurseWildcard .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_RemoveFile +configuration DSC_FileSystemObject_RemoveFile_Config { Import-DscResource -ModuleName FileSystemDsc @@ -259,7 +259,7 @@ configuration DSC_FileSystemObject_RemoveFile .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_RemoveFileWildcard +configuration DSC_FileSystemObject_RemoveFileWildcard_Config { Import-DscResource -ModuleName FileSystemDsc @@ -282,7 +282,7 @@ configuration DSC_FileSystemObject_RemoveFileWildcard .NOTES This requires that the temporary dir was created in the very first test #> -configuration DSC_FileSystemObject_RemoveDirRecurse +configuration DSC_FileSystemObject_RemoveDirRecurse_Config { Import-DscResource -ModuleName FileSystemDsc From 49a124c196ccfbb174b479e2ccdbb1defe30fd1d Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Thu, 19 Jan 2023 10:38:31 +0100 Subject: [PATCH 09/11] Last attempt to fix integration tests --- tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 b/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 index 21a7a4d..7730ff6 100644 --- a/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 +++ b/tests/Integration/DSC_FileSystemObject.Integration.Tests.ps1 @@ -24,6 +24,9 @@ BeforeAll { $script:dscModuleName = 'FileSystemDsc' $script:dscResourceFriendlyName = 'FileSystemObject' $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + $script:temproot = Join-Path -Path ([io.Path]::GetTempPath()) -ChildPath DscFileIntTest + $script:tempdir = Join-Path -Path $temproot -ChildPath Source + $script:tempDirDestination = Join-Path -Path $temproot -ChildPath Destination $script:testEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` From fcc02c023c1c6c4cd1be1f094f319a4172361634 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 17 Jun 2023 13:24:08 +0200 Subject: [PATCH 10/11] Fix style (instead of commenting) --- source/Classes/002.FileSystemObject.ps1 | 41 ++++++++++--------- .../ChecksumType.ps1} | 4 +- .../{enum/encoding.ps1 => Enum/Encoding.ps1} | 2 +- source/Enum/Ensure.ps1 | 6 +++ source/Enum/LinkBehavior.ps1 | 5 +++ source/Enum/ObjectType.ps1 | 6 +++ source/FileSystemDsc.psd1 | 1 + source/enum/ensure.ps1 | 6 --- source/enum/linkBehavior.ps1 | 5 --- source/enum/objectType.ps1 | 6 --- 10 files changed, 42 insertions(+), 40 deletions(-) rename source/{enum/checksumType.ps1 => Enum/ChecksumType.ps1} (61%) rename source/{enum/encoding.ps1 => Enum/Encoding.ps1} (87%) create mode 100644 source/Enum/Ensure.ps1 create mode 100644 source/Enum/LinkBehavior.ps1 create mode 100644 source/Enum/ObjectType.ps1 delete mode 100644 source/enum/ensure.ps1 delete mode 100644 source/enum/linkBehavior.ps1 delete mode 100644 source/enum/objectType.ps1 diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 index 3c17942..da6ee9b 100644 --- a/source/Classes/002.FileSystemObject.ps1 +++ b/source/Classes/002.FileSystemObject.ps1 @@ -20,6 +20,7 @@ class FileSystemDscReason .PARAMETER SourcePath If data should be copied, the source path to copy from. + .PARAMETER Ensure Indicates if destination should be created or removed. Values: Absent, Present. Default: Present. @@ -60,67 +61,67 @@ class FileSystemDscReason class FileSystemObject { [DscProperty(Key)] - [string] + [System.String] $DestinationPath [DscProperty()] - [string] + [System.String] $SourcePath [DscProperty()] - [ensure] - $Ensure = [ensure]::present + [Ensure] + $Ensure = [Ensure]::Present [DscProperty()] - [objectType] - $Type = [objectType]::directory + [ObjectType] + $Type = [ObjectType]::Directory [DscProperty()] - [string] + [System.String] $Contents [DscProperty()] - [checksumType] - $Checksum = [checksumType]::md5 + [ChecksumType] + $Checksum = [ChecksumType]::MD5 [DscProperty()] - [bool] + [System.Boolean] $Recurse = $false [DscProperty()] - [bool] + [System.Boolean] $Force = $false [DscProperty()] - [linkBehavior] - $Links = [linkBehavior]::follow + [LinkBehavior] + $Links = [LinkBehavior]::Follow [DscProperty()] - [string] + [System.String] $Group [DscProperty()] - [string] + [System.String] $Mode [DscProperty()] - [string] + [System.String] $Owner [DscProperty(NotConfigurable)] - [datetime] + [System.DateTime] $CreatedDate [DscProperty(NotConfigurable)] - [datetime] + [System.DateTim] $ModifiedDate [DscProperty()] - [encoding] + [Encoding] $Encoding = 'Default' [DscProperty()] - [bool] + [System.Boolean] $IgnoreTrailingWhitespace [DscProperty(NotConfigurable)] diff --git a/source/enum/checksumType.ps1 b/source/Enum/ChecksumType.ps1 similarity index 61% rename from source/enum/checksumType.ps1 rename to source/Enum/ChecksumType.ps1 index 90e2e89..990cf2d 100644 --- a/source/enum/checksumType.ps1 +++ b/source/Enum/ChecksumType.ps1 @@ -1,6 +1,6 @@ -enum checksumType +enum ChecksumType { - md5 + MD5 LastModifiedTime CreationTime } diff --git a/source/enum/encoding.ps1 b/source/Enum/Encoding.ps1 similarity index 87% rename from source/enum/encoding.ps1 rename to source/Enum/Encoding.ps1 index 16365ee..514ef72 100644 --- a/source/enum/encoding.ps1 +++ b/source/Enum/Encoding.ps1 @@ -1,4 +1,4 @@ -enum encoding +enum Encoding { ASCII Latin1 diff --git a/source/Enum/Ensure.ps1 b/source/Enum/Ensure.ps1 new file mode 100644 index 0000000..ac64f85 --- /dev/null +++ b/source/Enum/Ensure.ps1 @@ -0,0 +1,6 @@ +enum Ensure +{ + Present + Absent +} + diff --git a/source/Enum/LinkBehavior.ps1 b/source/Enum/LinkBehavior.ps1 new file mode 100644 index 0000000..ccb32f5 --- /dev/null +++ b/source/Enum/LinkBehavior.ps1 @@ -0,0 +1,5 @@ +enum LinkBehavior +{ + Follow + Manage +} diff --git a/source/Enum/ObjectType.ps1 b/source/Enum/ObjectType.ps1 new file mode 100644 index 0000000..b60952d --- /dev/null +++ b/source/Enum/ObjectType.ps1 @@ -0,0 +1,6 @@ +enum ObjectType +{ + File + Directory + Symboliclink +} diff --git a/source/FileSystemDsc.psd1 b/source/FileSystemDsc.psd1 index f83766e..d8c788d 100644 --- a/source/FileSystemDsc.psd1 +++ b/source/FileSystemDsc.psd1 @@ -1,5 +1,6 @@ @{ RootModule = 'FileSystemDsc.psm1' + # Version number of this module. ModuleVersion = '0.0.1' diff --git a/source/enum/ensure.ps1 b/source/enum/ensure.ps1 deleted file mode 100644 index d88b173..0000000 --- a/source/enum/ensure.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -enum ensure -{ - present - absent -} - diff --git a/source/enum/linkBehavior.ps1 b/source/enum/linkBehavior.ps1 deleted file mode 100644 index 2cd8327..0000000 --- a/source/enum/linkBehavior.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -enum linkBehavior -{ - follow - manage -} diff --git a/source/enum/objectType.ps1 b/source/enum/objectType.ps1 deleted file mode 100644 index fcbba0c..0000000 --- a/source/enum/objectType.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -enum objectType -{ - file - directory - symboliclink -} From 54a453f47be8cfdcb400574a2a560692ad093ec6 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 17 Jun 2023 13:34:37 +0200 Subject: [PATCH 11/11] Fix style change bvug --- source/Classes/002.FileSystemObject.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Classes/002.FileSystemObject.ps1 b/source/Classes/002.FileSystemObject.ps1 index da6ee9b..ee13354 100644 --- a/source/Classes/002.FileSystemObject.ps1 +++ b/source/Classes/002.FileSystemObject.ps1 @@ -113,7 +113,7 @@ class FileSystemObject $CreatedDate [DscProperty(NotConfigurable)] - [System.DateTim] + [System.DateTime] $ModifiedDate [DscProperty()]