functions/shares/Test-DCShare.ps1
function Test-DCShare { <# .SYNOPSIS Tests all DCs in the target domain for share compliance. .DESCRIPTION Tests all DCs in the target domain, by comparing existing shares with the list of defined shares. Use Register-DCShare (or an ADMF Context) to define shares. .PARAMETER Server The server / domain to work with. .PARAMETER Credential The credentials to use for this operation. .PARAMETER EnableException This parameters disables user-friendly warnings and enables the throwing of exceptions. This is less user friendly, but allows catching exceptions in calling scripts. .EXAMPLE PS C:\> Test-DCShare -Server contoso.com Tests all DCs in the domain contoso.com, whether their shares are as configured. #> [CmdletBinding()] Param ( [PSFComputer] $Server, [PSCredential] $Credential, [switch] $EnableException ) begin { $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential $parameters['Debug'] = $false Assert-ADConnection @parameters -Cmdlet $PSCmdlet Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet Assert-Configuration -Type Shares -Cmdlet $PSCmdlet Set-DCDomainContext @parameters $domainControllers = Get-DomainController @parameters $cimCred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential #region Utility Functions function ConvertFrom-ShareConfiguration { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $ShareConfiguration, [hashtable] $Parameters ) process { $cfgHash = $ShareConfiguration | ConvertTo-PSFHashtable $cfgHash.Name = $cfgHash.Name | Resolve-String -ArgumentList $Parameters $cfgHash.Path = $cfgHash.Path | Resolve-String -ArgumentList $Parameters $cfgHash.Description = $cfgHash.Description | Resolve-String -ArgumentList $Parameters $cfgHash.AccessIdentityIntegrity = $true $cfgHash.FullAccess = foreach ($entry in $cfgHash.FullAccess) { try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } catch { Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration $cfgHash.AccessIdentityIntegrity = $false } } $cfgHash.WriteAccess = foreach ($entry in $cfgHash.WriteAccess) { try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } catch { Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration $cfgHash.AccessIdentityIntegrity = $false } } $cfgHash.ReadAccess = foreach ($entry in $cfgHash.ReadAccess) { try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } catch { Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration $cfgHash.AccessIdentityIntegrity = $false } } [pscustomobject]$cfgHash } } function Compare-ShareAccess { [CmdletBinding()] param ( $Configuration, $ShareAccess, [hashtable] $Parameters ) $access = @{ Full = @() Change = @() Read = @() } foreach ($accessItem in $ShareAccess) { $access["$($accessItem.AccessRight)"] += ($accessItem.AccountName | Resolve-Principal @Parameters -OutputType SID) -as [string] } #region Compare Defined with current state foreach ($entity in $Configuration.FullAccess) { if ($entity -notin $access.Full) { New-AccessChange -AccessRight Full -Identity $entity -Action Add } } foreach ($entity in $Configuration.WriteAccess) { if ($entity -notin $access.Change) { New-AccessChange -AccessRight Change -Identity $entity -Action Add } } foreach ($entity in $Configuration.ReadAccess) { if ($entity -notin $access.Read) { New-AccessChange -AccessRight Read -Identity $entity -Action Add } } #endregion Compare Defined with current state # If we will not remove any rights, no point inspecting the existing access rights if ($Configuration.AccessMode -eq 'Additive') { return } #region Compare current with defined state #region Full Access foreach ($entity in $access.Full) { if ($entity -notin $Configuration.FullAccess) { if ($Configuration.AccessMode -eq 'Constrained') { New-AccessChange -AccessRight Full -Identity $entity -Action Remove continue } if ($entity -notin $Configuration.WriteAccess -and $entity -notin $Configuration.ReadAccess) { continue } New-AccessChange -AccessRight Full -Identity $entity -Action Remove } } #endregion Full Access #region Write Access foreach ($entity in $access.Change) { if ($entity -notin $Configuration.WriteAccess) { if ($Configuration.AccessMode -eq 'Constrained') { New-AccessChange -AccessRight Change -Identity $entity -Action Remove continue } if ($entity -notin $Configuration.FullAccess -and $entity -notin $Configuration.ReadAccess) { continue } New-AccessChange -AccessRight Change -Identity $entity -Action Remove } } #endregion Write Access #region Read Access foreach ($entity in $access.Read) { if ($entity -notin $Configuration.ReadAccess) { if ($Configuration.AccessMode -eq 'Constrained') { New-AccessChange -AccessRight Read -Identity $entity -Action Remove continue } if ($entity -notin $Configuration.FullAccess -and $entity -notin $Configuration.WriteAccess) { continue } New-AccessChange -AccessRight Read -Identity $entity -Action Remove } } #endregion Read Access #endregion Compare current with defined state } function New-AccessChange { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [ValidateSet('Full','Change','Read')] [string] $AccessRight, [string] $Identity, [ValidateSet('Add','Remove')] [string] $Action ) [PSCustomObject]@{ PSTypeName = 'DCManagement.Share.AccessChange' Action = $Action AccessRight = $AccessRight Identity = $Identity } } #endregion Utility Functions } process { foreach ($domainController in $domainControllers) { $results = @{ ObjectType = 'Share' Server = $domainController.Name } Write-PSFMessage -String 'Test-DCShare.Processing' -StringValues $domainController.Name try { $cimSession = New-CimSession -ComputerName $domainController.Name @cimCred -ErrorAction Stop } catch { Stop-PSFFunction -String 'Test-DCShare.CimSession.Failed' -StringValues $domainController.Name -EnableException $EnableException -Cmdlet $PSCmdlet -Continue -Target $domainController.Name -ErrorRecord $_ } $shareConfigurations = Get-DCShare | Where-Object { $_.ServerRole -eq 'ALL' -or ($_.ServerRole -eq 'FSMO' -and $domainController.IsFSMO) -or ($_.ServerRole -eq 'PDC' -and $domainController.IsPDCEmulator) } | ConvertFrom-ShareConfiguration -Parameters $parameters $shares = Get-SmbShare -CimSession $cimSession -IncludeHidden | Add-Member -MemberType NoteProperty -Name ComputerName -Value $domainController.Name -Force -PassThru foreach ($share in $shares) { $sResults = $results.Clone() $sResults.Identity = '\\{0}\{1}' -f $share.ComputerName, $share.Name $sResults.ADObject = $share #region Share exists, but is not defined if ($share.Name -notin $shareConfigurations.Name) { # The special builtin shares will not trigger delete actions if ($share.Special) { continue } if ($share.Name -in 'NETLOGON','SYSVOL') { continue } New-Testresult @sResults -Type Delete continue } #endregion Share exists, but is not defined $configuration = $shareConfigurations | Where-Object Name -EQ $share.Name $sResults.Configuration = $configuration #region Handle Property Settings [System.Collections.ArrayList]$changes = @() Compare-Property -Configuration $configuration -ADObject $share -Changes $changes -Property Path Compare-Property -Configuration $configuration -ADObject $share -Changes $changes -Property Description if ($changes) { New-Testresult @sResults -Type Update -Changed $changes.ToArray() } #endregion Handle Property Settings #region Delegation if (-not $configuration.AccessIdentityIntegrity) { Write-PSFMessage -Level Warning -String 'Test-DCShare.Access.IntegrityError' -StringValues $share.Name, $domainController.Name -Tag panic, error, fail continue } $access = Get-SmbShareAccess -CimSession $cimSession -Name $share.Name $delta = Compare-ShareAccess -Configuration $configuration -ShareAccess $access -Parameters $parameters if ($delta) { New-Testresult @sResults -Type AccessUpdate -Changed $delta } #endregion Delegation } foreach ($cfgShare in $shareConfigurations) { if ($cfgShare.Name -in $shares.Name) { continue } New-TestResult @results -Type New -Identity "\\$($domainController.Name)\$($cfgShare.Name)" -Configuration $cfgShare } Remove-CimSession -CimSession $cimSession -ErrorAction Ignore -WhatIf:$false -Confirm:$false } } } |