ComputerManagementDsc.psm1

#Region '.\prefix.ps1' 0
$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common'
Import-Module -Name $script:dscResourceCommonModulePath

$script:computerManagementDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/ComputerManagementDsc.Common'
Import-Module -Name $script:computerManagementDscCommonModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'
#EndRegion '.\prefix.ps1' 8
#Region '.\Enum\1.Ensure.ps1' 0
<#
    .SYNOPSIS
        The possible states for the DSC resource parameter Ensure.
#>

enum Ensure
{
    Present
    Absent
}
#EndRegion '.\Enum\1.Ensure.ps1' 10
#Region '.\Classes\010.ResourceBase.ps1' 0
<#
    .SYNOPSIS
        A class with methods that are equal for all class-based resources.
 
    .DESCRIPTION
        A class with methods that are equal for all class-based resources.
 
    .NOTES
        This class should be able to be inherited by all DSC resources. This class
        shall not contain any DSC properties, neither shall it contain anything
        specific to only a single resource.
#>


class ResourceBase
{
    # Property for holding localization strings
    hidden [System.Collections.Hashtable] $localizedData = @{}

    # Property for derived class to set properties that should not be enforced.
    hidden [System.String[]] $ExcludeDscProperties = @()

    # Default constructor
    ResourceBase()
    {
        <#
            TODO: When this fails, for example when the localized string file is missing
                  the LCM returns the error 'Failed to create an object of PowerShell
                  class SqlDatabasePermission' instead of the actual error that occurred.
        #>

        $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse)
    }

    [ResourceBase] Get()
    {
        $this.Assert()

        # Get all key properties.
        $keyProperty = $this | Get-DscProperty -Type 'Key'

        Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress))

        $getCurrentStateResult = $this.GetCurrentState($keyProperty)

        $dscResourceObject = [System.Activator]::CreateInstance($this.GetType())

        # Set values returned from the derived class' GetCurrentState().
        foreach ($propertyName in $this.PSObject.Properties.Name)
        {
            if ($propertyName -in @($getCurrentStateResult.Keys))
            {
                $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName
            }
        }

        $keyPropertyAddedToCurrentState = $false

        # Set key property values unless it was returned from the derived class' GetCurrentState().
        foreach ($propertyName in $keyProperty.Keys)
        {
            if ($propertyName -notin @($getCurrentStateResult.Keys))
            {
                # Add the key value to the instance to be returned.
                $dscResourceObject.$propertyName = $this.$propertyName

                $keyPropertyAddedToCurrentState = $true
            }
        }

        if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure'))
        {
            # Evaluate if we should set Ensure property.
            if ($keyPropertyAddedToCurrentState)
            {
                <#
                    A key property was added to the current state, assume its because
                    the object did not exist in the current state. Set Ensure to Absent.
                #>

                $dscResourceObject.Ensure = [Ensure]::Absent
                $getCurrentStateResult.Ensure = [Ensure]::Absent
            }
            else
            {
                $dscResourceObject.Ensure = [Ensure]::Present
                $getCurrentStateResult.Ensure = [Ensure]::Present
            }
        }

        <#
            Returns all enforced properties not in desires state, or $null if
            all enforced properties are in desired state.
        #>

        $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, @())

        <#
            Return the correct values for Reasons property if the derived DSC resource
            has such property and it hasn't been already set by GetCurrentState().
        #>

        if (($this | Test-ResourceHasDscProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons'))
        {
            # Always return an empty array if all properties are in desired state.
            $dscResourceObject.Reasons = $propertiesNotInDesiredState |
                ConvertTo-Reason -ResourceName $this.GetType().Name
        }

        # Return properties.
        return $dscResourceObject
    }

    [void] Set()
    {
        # Get all key properties.
        $keyProperty = $this | Get-DscProperty -Type 'Key'

        Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress))

        $this.Assert()

        <#
            Returns all enforced properties not in desires state, or $null if
            all enforced properties are in desired state.
        #>

        $propertiesNotInDesiredState = $this.Compare()

        if ($propertiesNotInDesiredState)
        {
            $propertiesToModify = $propertiesNotInDesiredState | ConvertFrom-CompareResult

            $propertiesToModify.Keys |
                ForEach-Object -Process {
                    Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_)
                }

            <#
                Call the Modify() method with the properties that should be enforced
                and was not in desired state.
            #>

            $this.Modify($propertiesToModify)
        }
        else
        {
            Write-Verbose -Message $this.localizedData.NoPropertiesToSet
        }
    }

    [System.Boolean] Test()
    {
        # Get all key properties.
        $keyProperty = $this | Get-DscProperty -Type 'Key'

        Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress))

        $this.Assert()

        $isInDesiredState = $true

        <#
            Returns all enforced properties not in desires state, or $null if
            all enforced properties are in desired state.
        #>

        $propertiesNotInDesiredState = $this.Compare()

        if ($propertiesNotInDesiredState)
        {
            $isInDesiredState = $false
        }

        if ($isInDesiredState)
        {
            Write-Verbose $this.localizedData.InDesiredState
        }
        else
        {
            Write-Verbose $this.localizedData.NotInDesiredState
        }

        return $isInDesiredState
    }

    <#
        Returns a hashtable containing all properties that should be enforced and
        are not in desired state, or $null if all enforced properties are in
        desired state.
 
        This method should normally not be overridden.
    #>

    hidden [System.Collections.Hashtable[]] Compare()
    {
        # Get the current state, all properties except Read properties .
        $currentState = $this.Get() | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional')

        return $this.Compare($currentState, @())
    }

    <#
        Returns a hashtable containing all properties that should be enforced and
        are not in desired state, or $null if all enforced properties are in
        desired state.
 
        This method should normally not be overridden.
    #>

    hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties)
    {
        # Get the desired state, all assigned properties that has an non-null value.
        $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue

        $CompareDscParameterState = @{
            CurrentValues     = $currentState
            DesiredValues     = $desiredState
            Properties        = $desiredState.Keys
            ExcludeProperties = ($excludeProperties + $this.ExcludeDscProperties) | Select-Object -Unique
            IncludeValue      = $true
            # This is needed to sort complex types.
            SortArrayValues   = $true
        }

        <#
            Returns all enforced properties not in desires state, or $null if
            all enforced properties are in desired state.
        #>

        return (Compare-DscParameterState @CompareDscParameterState)
    }

    # This method should normally not be overridden.
    hidden [void] Assert()
    {
        # Get the properties that has a non-null value and is not of type Read.
        $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue

        $this.AssertProperties($desiredState)
    }

    <#
        This method can be overridden if resource specific property asserts are
        needed. The parameter properties will contain the properties that was
        assigned a value.
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')]
    hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
    {
    }

    <#
        This method must be overridden by a resource. The parameter properties will
        contain the properties that should be enforced and that are not in desired
        state.
    #>

    hidden [void] Modify([System.Collections.Hashtable] $properties)
    {
        throw $this.localizedData.ModifyMethodNotImplemented
    }

    <#
        This method must be overridden by a resource. The parameter properties will
        contain the key properties.
    #>

    hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties)
    {
        throw $this.localizedData.GetCurrentStateMethodNotImplemented
    }
}
#EndRegion '.\Classes\010.ResourceBase.ps1' 261
#Region '.\Classes\020.PSResourceRepository.ps1' 0
<#
    .SYNOPSIS
        A class for configuring PowerShell Repositories.
 
    .PARAMETER Ensure
        If the repository should be present or absent on the server
        being configured. Default values is 'Present'.
 
    .PARAMETER Name
        Specifies the name of the repository to manage.
 
    .PARAMETER SourceLocation
        Specifies the URI for discovering and installing modules from
        this repository. A URI can be a NuGet server feed, HTTP, HTTPS,
        FTP or file location.
 
    .PARAMETER Credential
        Specifies credentials of an account that has rights to register a repository.
 
    .PARAMETER ScriptSourceLocation
        Specifies the URI for the script source location.
 
    .PARAMETER PublishLocation
        Specifies the URI of the publish location. For example, for
        NuGet-based repositories, the publish location is similar
        to http://someNuGetUrl.com/api/v2/Packages.
 
    .PARAMETER ScriptPublishLocation
        Specifies the URI for the script publish location.
 
    .PARAMETER Proxy
        Specifies the URI of the proxy to connect to this PSResourceRepository.
 
    .PARAMETER ProxyCredential
        Specifies the Credential to connect to the PSResourceRepository proxy.
 
    .PARAMETER InstallationPolicy
        Specifies the installation policy. Valid values are 'Trusted'
        or 'Untrusted'. The default value is 'Untrusted'.
 
    .PARAMETER PackageManagementProvider
        Specifies a OneGet package provider. Default value is 'NuGet'.
 
    .PARAMETER Default
        Specifies whether to set the default properties for the default PSGallery PSRepository.
        Default may only be used in conjunction with a PSRepositoryResource named PSGallery.
        The properties SourceLocation, ScriptSourceLocation, PublishLocation, ScriptPublishLocation, Credential,
        and PackageManagementProvider may not be used in conjunction with Default.
        When the Default parameter is used, properties are not enforced when PSGallery properties are changed outside of Dsc.
 
    .EXAMPLE
        Invoke-DscResource -ModuleName ComputerManagementDsc -Name PSResourceRepository -Method Get -Property @{
            Name = 'PSTestRepository'
            SourceLocation = 'https://www.nuget.org/api/v2'
            ScriptSourceLocation = 'https://www.nuget.org/api/v2/package/'
            PublishLocation = 'https://www.nuget.org/api/v2/items/psscript/'
            ScriptPublishLocation = 'https://www.nuget.org/api/v2/package/'
            InstallationPolicy = 'Trusted'
            PackageManagementProvider = 'NuGet'
        }
        This example shows how to call the resource using Invoke-DscResource.
#>

[DscResource()]
class PSResourceRepository : ResourceBase
{
    [DscProperty()]
    [Ensure]
    $Ensure = [Ensure]::Present

    [DscProperty(Key)]
    [System.String]
    $Name

    [DscProperty()]
    [System.String]
    $SourceLocation

    [DscProperty()]
    [PSCredential]
    $Credential

    [DscProperty()]
    [System.String]
    $ScriptSourceLocation

    [DscProperty()]
    [System.String]
    $PublishLocation

    [DscProperty()]
    [System.String]
    $ScriptPublishLocation

    [DscProperty()]
    [System.String]
    $Proxy

    [DscProperty()]
    [pscredential]
    $ProxyCredential

    [DscProperty()]
    [ValidateSet('Untrusted', 'Trusted')]
    [System.String]
    $InstallationPolicy

    [DscProperty()]
    [System.String]
    $PackageManagementProvider

    [DscProperty()]
    [Nullable[System.Boolean]]
    $Default

    PSResourceRepository () : base ()
    {
        # These properties will not be enforced.
        $this.ExcludeDscProperties = @(
            'Name',
            'Default'
        )
    }

    [PSResourceRepository] Get()
    {
        return ([ResourceBase]$this).Get()
    }

    [void] Set()
    {
        ([ResourceBase]$this).Set()
    }

    [Boolean] Test()
    {
        return ([ResourceBase] $this).Test()
    }

    hidden [void] Modify([System.Collections.Hashtable] $properties)
    {
        $params = @{
            Name = $this.Name
        }

        if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq 'Absent' -and $this.Ensure -eq 'Absent')
        {
            # Ensure was not in desired state so the repository should be removed
            Write-Verbose -Message ($this.localizedData.RemoveExistingRepository -f $this.Name)

            Unregister-PSRepository @params

            return
        }
        elseif ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq 'Present' -and $this.Ensure -eq 'Present')
        {
            # Ensure was not in desired state so the repository should be created
            $register = $true

        }
        else
        {
            # Repository exist but one or more properties are not in desired state
            $register = $false
        }

        foreach ($key in $properties.Keys.Where({ $_ -ne 'Ensure' }))
        {
            $params[$key] = $properties.$key
        }

        if ($register)
        {
            if ($this.Name -eq 'PSGallery')
            {
                Write-Verbose -Message ($this.localizedData.RegisterDefaultRepository -f $this.Name)

                Register-PSRepository -Default

                #* The user may have specified Proxy & Proxy Credential, or InstallationPolicy params
                Set-PSRepository @params
            }
            else
            {
                if ([System.String]::IsNullOrEmpty($this.SourceLocation))
                {
                    $errorMessage = $this.LocalizedData.SourceLocationRequiredForRegistration

                    New-InvalidArgumentException -ArgumentName 'SourceLocation' -Message $errorMessage
                }

                if ($params.Keys -notcontains 'SourceLocation')
                {
                    $params['SourceLocation'] = $this.SourceLocation
                }

                Write-Verbose -Message ($this.localizedData.RegisterRepository -f $this.Name, $this.SourceLocation)

                Register-PSRepository @params
            }
        }
        else
        {
            Write-Verbose -Message ($this.localizedData.UpdateRepository -f $this.Name, $this.SourceLocation)

            Set-PSRepository @params
        }
    }

    hidden [System.Collections.Hashtable] GetCurrentState ([System.Collections.Hashtable] $properties)
    {
        $returnValue = @{
            Ensure = [Ensure]::Absent
            Name   = $this.Name
        }

        Write-Verbose -Message ($this.localizedData.GetTargetResourceMessage -f $this.Name)

        $repository = Get-PSRepository -Name $this.Name -ErrorAction SilentlyContinue

        if ($repository)
        {
            $returnValue.Ensure                    = [Ensure]::Present
            $returnValue.SourceLocation            = $repository.SourceLocation
            $returnValue.ScriptSourceLocation      = $repository.ScriptSourceLocation
            $returnValue.PublishLocation           = $repository.PublishLocation
            $returnValue.ScriptPublishLocation     = $repository.ScriptPublishLocation
            $returnValue.Proxy                     = $repository.Proxy
            $returnValue.ProxyCredential           = $repository.ProxyCredental
            $returnValue.InstallationPolicy        = $repository.InstallationPolicy
            $returnValue.PackageManagementProvider = $repository.PackageManagementProvider
        }
        else
        {
            Write-Verbose -Message ($this.localizedData.RepositoryNotFound -f $this.Name)
        }

        return $returnValue
    }

    <#
        The parameter properties will contain the properties that was
        assigned a value.
    #>

    hidden [void] AssertProperties([System.Collections.Hashtable] $properties)
    {
        Assert-Module -ModuleName PowerShellGet
        Assert-Module -ModuleName PackageManagement

        $assertBoundParameterParameters = @{
            BoundParameterList = $properties
            MutuallyExclusiveList1 = @(
                'Default'
            )
            MutuallyExclusiveList2 = @(
                'SourceLocation'
                'PackageSourceLocation'
                'ScriptPublishLocation'
                'ScriptSourceLocation'
                'Credential'
                'PackageManagementProvider'
            )
        }

        Assert-BoundParameter @assertBoundParameterParameters

        if ($this.Name -eq 'PSGallery')
        {
            if (-not $this.Default -and $this.Ensure -eq 'Present')
            {
                $errorMessage = $this.localizedData.NoDefaultSettingsPSGallery

                New-InvalidArgumentException -ArgumentName 'Default' -Message $errorMessage
            }
        }
        else
        {
            if ($this.Default)
            {
                $errorMessage = $this.localizedData.DefaultSettingsNoPSGallery

                New-InvalidArgumentException -ArgumentName 'Default' -Message $errorMessage
            }
        }

        if ($this.ProxyCredential -and (-not $this.Proxy))
        {
            $errorMessage = $this.localizedData.ProxyCredentialPassedWithoutProxyUri

            New-InvalidArgumentException -ArgumentName 'ProxyCredential' -Message $errorMessage
        }
    }
}
#EndRegion '.\Classes\020.PSResourceRepository.ps1' 293
#Region '.\Private\ConvertFrom-CompareResult.ps1' 0
<#
    .SYNOPSIS
        Returns a hashtable with property name and their expected value.
 
    .PARAMETER CompareResult
       The result from Compare-DscParameterState.
 
    .EXAMPLE
        ConvertFrom-CompareResult -CompareResult (Compare-DscParameterState)
 
        Returns a hashtable that contain all the properties not in desired state
        and their expected value.
 
    .OUTPUTS
        [System.Collections.Hashtable]
#>

function ConvertFrom-CompareResult
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Collections.Hashtable[]]
        $CompareResult
    )

    begin
    {
        $returnHashtable = @{}
    }

    process
    {
        $CompareResult | ForEach-Object -Process {
            $returnHashtable[$_.Property] = $_.ExpectedValue
        }
    }

    end
    {
        return $returnHashtable
    }
}
#EndRegion '.\Private\ConvertFrom-CompareResult.ps1' 45
#Region '.\Private\ConvertTo-Reason.ps1' 0
<#
    .SYNOPSIS
        Returns a array of the type `[Reason]`.
 
    .DESCRIPTION
        This command converts the array of properties that is returned by the command
        `Compare-DscParameterState`. The result is an array of the type `[Reason]` that
        can be returned in a DSC resource's property **Reasons**.
 
    .PARAMETER Property
       The result from the command Compare-DscParameterState.
 
    .PARAMETER ResourceName
       The name of the resource. Will be used to populate the property Code with
       the correct value.
 
    .EXAMPLE
        ConvertTo-Reason -Property (Compare-DscParameterState) -ResourceName 'MyResource'
 
        Returns an array of `[Reason]` that contain all the properties not in desired
        state and why a specific property is not in desired state.
 
    .OUTPUTS
        [Reason[]]
#>

function ConvertTo-Reason
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')]
    [CmdletBinding()]
    [OutputType([Reason[]])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [System.Collections.Hashtable[]]
        $Property,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ResourceName
    )

    begin
    {
        # Always return an empty array if there are no properties to add.
        $reasons = [Reason[]] @()
    }

    process
    {
        foreach ($currentProperty in $Property)
        {
            if ($currentProperty.ExpectedValue -is [System.Enum])
            {
                # Return the string representation of the value (instead of the numeric value).
                $propertyExpectedValue = $currentProperty.ExpectedValue.ToString()
            }
            else
            {
                $propertyExpectedValue = $currentProperty.ExpectedValue
            }

            if ($property.ActualValue -is [System.Enum])
            {
                # Return the string representation of the value so that conversion to json is correct.
                $propertyActualValue = $currentProperty.ActualValue.ToString()
            }
            else
            {
                $propertyActualValue = $currentProperty.ActualValue
            }

            <#
                In PowerShell 7 the command ConvertTo-Json returns 'null' on null
                value, but not in Windows PowerShell. Switch to output empty string
                if value is null.
            #>

            if ($PSVersionTable.PSEdition -eq 'Desktop')
            {
                if ($null -eq $propertyExpectedValue)
                {
                    $propertyExpectedValue = ''
                }

                if ($null -eq $propertyActualValue)
                {
                    $propertyActualValue = ''
                }
            }

            # Convert the value to Json to be able to easily visualize complex types
            $propertyActualValueJson = $propertyActualValue | ConvertTo-Json -Compress
            $propertyExpectedValueJson = $propertyExpectedValue | ConvertTo-Json -Compress

            # If the property name contain the word Path, remove '\\' from path.
            if ($currentProperty.Property -match 'Path')
            {
                $propertyActualValueJson = $propertyActualValueJson -replace '\\\\', '\'
                $propertyExpectedValueJson = $propertyExpectedValueJson -replace '\\\\', '\'
            }

            $reasons += [Reason] @{
                Code   = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property
                # Convert the object to JSON to handle complex types.
                Phrase = 'The property {0} should be {1}, but was {2}' -f @(
                    $currentProperty.Property,
                    $propertyExpectedValueJson,
                    $propertyActualValueJson
                )
            }
        }
    }

    end
    {
        return $reasons
    }
}
#EndRegion '.\Private\ConvertTo-Reason.ps1' 120
#Region '.\Private\Get-ClassName.ps1' 0
<#
    .SYNOPSIS
        Get the class name of the passed object, and optional an array with
        all inherited classes.
 
    .PARAMETER InputObject
       The object to be evaluated.
 
    .PARAMETER Recurse
       Specifies if the class name of inherited classes shall be returned. The
       recursive stops when the first object of the type `[System.Object]` is
       found.
 
    .EXAMPLE
        Get-ClassName -InputObject $this -Recurse
 
        Get the class name of the current instance and all the inherited (parent)
        classes.
 
    .OUTPUTS
        [System.String[]]
#>

function Get-ClassName
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')]
    [CmdletBinding()]
    [OutputType([System.String[]])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]
        $InputObject,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $Recurse
    )

    # Create a list of the inherited class names
    $class = @($InputObject.GetType().FullName)

    if ($Recurse.IsPresent)
    {
        $parentClass = $InputObject.GetType().BaseType

        while ($parentClass -ne [System.Object])
        {
            $class += $parentClass.FullName

            $parentClass = $parentClass.BaseType
        }
    }

    return , [System.String[]] $class
}
#EndRegion '.\Private\Get-ClassName.ps1' 56
#Region '.\Private\Get-DscProperty.ps1' 0

<#
    .SYNOPSIS
        Returns DSC resource properties that is part of a class-based DSC resource.
 
    .DESCRIPTION
        Returns DSC resource properties that is part of a class-based DSC resource.
        The properties can be filtered using name, type, or has been assigned a value.
 
    .PARAMETER InputObject
        The object that contain one or more key properties.
 
    .PARAMETER Name
        Specifies one or more property names to return. If left out all properties
        are returned.
 
    .PARAMETER Type
        Specifies one or more property types to return. If left out all property
        types are returned.
 
    .PARAMETER HasValue
        Specifies to return only properties that has been assigned a non-null value.
        If left out all properties are returned regardless if there is a value
        assigned or not.
 
    .EXAMPLE
        Get-DscProperty -InputObject $this
 
        Returns all DSC resource properties of the DSC resource.
 
    .EXAMPLE
        Get-DscProperty -InputObject $this -Name @('MyProperty1', 'MyProperty2')
 
        Returns the specified DSC resource properties names of the DSC resource.
 
    .EXAMPLE
        Get-DscProperty -InputObject $this -Type @('Mandatory', 'Optional')
 
        Returns the specified DSC resource property types of the DSC resource.
 
    .EXAMPLE
        Get-DscProperty -InputObject $this -Type @('Optional') -HasValue
 
        Returns the specified DSC resource property types of the DSC resource,
        but only those properties that has been assigned a non-null value.
 
    .OUTPUTS
        [System.Collections.Hashtable]
#>

function Get-DscProperty
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]
        $InputObject,

        [Parameter()]
        [System.String[]]
        $Name,

        [Parameter()]
        [System.String[]]
        $ExcludeName,

        [Parameter()]
        [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')]
        [System.String[]]
        $Type,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $HasValue
    )

    $property = $InputObject.PSObject.Properties.Name |
        Where-Object -FilterScript {
            <#
                Return all properties if $Name is not assigned, or if assigned
                just those properties.
            #>

            (-not $Name -or $_ -in $Name) -and

            <#
                Return all properties if $ExcludeName is not assigned. Skip
                property if it is included in $ExcludeName.
            #>

            (-not $ExcludeName -or ($_ -notin $ExcludeName)) -and

            # Only return the property if it is a DSC property.
            $InputObject.GetType().GetMember($_).CustomAttributes.Where(
                {
                    $_.AttributeType.Name -eq 'DscPropertyAttribute'
                }
            )
        }

    if (-not [System.String]::IsNullOrEmpty($property))
    {
        if ($PSBoundParameters.ContainsKey('Type'))
        {
            $propertiesOfType = @()

            $propertiesOfType += $property | Where-Object -FilterScript {
                $InputObject.GetType().GetMember($_).CustomAttributes.Where(
                    {
                        <#
                            To simplify the code, ignoring that this will compare
                            MemberNAme against type 'Optional' which does not exist.
                        #>

                        $_.NamedArguments.MemberName -in $Type
                    }
                ).NamedArguments.TypedValue.Value -eq $true
            }

            # Include all optional parameter if it was requested.
            if ($Type -contains 'Optional')
            {
                $propertiesOfType += $property | Where-Object -FilterScript {
                    $InputObject.GetType().GetMember($_).CustomAttributes.Where(
                        {
                            $_.NamedArguments.MemberName -notin @('Key', 'Mandatory', 'NotConfigurable')
                        }
                    )
                }
            }

            $property = $propertiesOfType
        }
    }

    # Return a hashtable containing each key property and its value.
    $getPropertyResult = @{}

    foreach ($currentProperty in $property)
    {
        if ($HasValue.IsPresent)
        {
            $isAssigned = Test-ResourceDscPropertyIsAssigned -Name $currentProperty -InputObject $InputObject

            if (-not $isAssigned)
            {
                continue
            }
        }

        $getPropertyResult.$currentProperty = $InputObject.$currentProperty
    }

    return $getPropertyResult
}
#EndRegion '.\Private\Get-DscProperty.ps1' 154
#Region '.\Private\Get-LocalizedDataRecursive.ps1' 0
<#
    .SYNOPSIS
        Get the localization strings data from one or more localization string files.
        This can be used in classes to be able to inherit localization strings
        from one or more parent (base) classes.
 
        The order of class names passed to parameter `ClassName` determines the order
        of importing localization string files. First entry's localization string file
        will be imported first, then next entry's localization string file, and so on.
        If the second (or any consecutive) entry's localization string file contain a
        localization string key that existed in a previous imported localization string
        file that localization string key will be ignored. Making it possible for a
        child class to override localization strings from one or more parent (base)
        classes.
 
    .PARAMETER ClassName
        An array of class names, normally provided by `Get-ClassName -Recurse`.
 
    .EXAMPLE
        Get-LocalizedDataRecursive -ClassName $InputObject.GetType().FullName
 
        Returns a hashtable containing all the localized strings for the current
        instance.
 
    .EXAMPLE
        Get-LocalizedDataRecursive -ClassName (Get-ClassNamn -InputObject $this -Recurse)
 
        Returns a hashtable containing all the localized strings for the current
        instance and any inherited (parent) classes.
 
    .OUTPUTS
        [System.Collections.Hashtable]
#>

function Get-LocalizedDataRecursive
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.String[]]
        $ClassName
    )

    begin
    {
        $localizedData = @{}
    }

    process
    {
        foreach ($name in $ClassName)
        {
            if ($name -match '\.psd1')
            {
                # Assume we got full file name.
                $localizationFileName = $name
            }
            else
            {
                # Assume we only got class name.
                $localizationFileName = '{0}.strings.psd1' -f $name
            }

            Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName)

            # Get localized data for the class
            $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop'

            # Append only previously unspecified keys in the localization data
            foreach ($key in $classLocalizationStrings.Keys)
            {
                if (-not $localizedData.ContainsKey($key))
                {
                    $localizedData[$key] = $classLocalizationStrings[$key]
                }
            }
        }
    }

    end
    {
        Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON))

        return $localizedData
    }
}
#EndRegion '.\Private\Get-LocalizedDataRecursive.ps1' 88
#Region '.\Private\Test-ResourceDscPropertyIsAssigned.ps1' 0
<#
    .SYNOPSIS
        Tests whether the class-based resource property is assigned a non-null value.
 
    .DESCRIPTION
        Tests whether the class-based resource property is assigned a non-null value.
 
    .PARAMETER InputObject
        Specifies the object that contain the property.
 
    .PARAMETER Name
        Specifies the name of the property.
 
    .EXAMPLE
        Test-ResourceDscPropertyIsAssigned -InputObject $this -Name 'MyDscProperty'
 
        Returns $true or $false whether the property is assigned or not.
 
    .OUTPUTS
        [System.Boolean]
#>

function Test-ResourceDscPropertyIsAssigned
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]
        $InputObject,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Name
    )

    $isAssigned = -not ($null -eq $InputObject.$Name)

    return $isAssigned
}
#EndRegion '.\Private\Test-ResourceDscPropertyIsAssigned.ps1' 41
#Region '.\Private\Test-ResourceHasDscProperty.ps1' 0
<#
    .SYNOPSIS
        Tests whether the class-based resource has the specified property.
 
    .DESCRIPTION
        Tests whether the class-based resource has the specified property.
 
    .PARAMETER InputObject
        Specifies the object that should be tested for existens of the specified
        property.
 
    .PARAMETER Name
        Specifies the name of the property.
 
    .PARAMETER HasValue
        Specifies if the property should be evaluated to have a non-value. If
        the property exist but is assigned `$null` the command returns `$false`.
 
    .EXAMPLE
        Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty'
 
        Returns $true or $false whether the property exist or not.
 
    .EXAMPLE
        Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' -HasValue
 
        Returns $true if the property exist and is assigned a non-null value, if not
        $false is returned.
 
    .OUTPUTS
        [System.Boolean]
#>

function Test-ResourceHasDscProperty
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [PSObject]
        $InputObject,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [Parameter()]
        [System.Management.Automation.SwitchParameter]
        $HasValue
    )

    $hasProperty = $false

    $isDscProperty = (Get-DscProperty @PSBoundParameters).ContainsKey($Name)

    if ($isDscProperty)
    {
        $hasProperty = $true
    }

    return $hasProperty
}
#EndRegion '.\Private\Test-ResourceHasDscProperty.ps1' 63