Modules/DhcpServerDsc.Common/DhcpServerDsc.Common.psm1

#Region '.\prefix.ps1' -1

$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '../../Modules/DscResource.Common'

Import-Module -Name $script:resourceHelperModulePath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'
#EndRegion '.\prefix.ps1' 6
#Region '.\Public\Assert-ScopeParameter.ps1' -1

<#
    .SYNOPSIS
        Function to assert if values of ScopeId/SubnetMask/IPStartRange/IPEndRange make sense.
 
    .DESCRIPTION
        Internal function used to assert if value of following parameters are correct:
        - ScopeID
        - SubnetMask
        - IPStartRange
        - IPEndRange
 
        It validates them against simple rules:
        - Has to be correct (IPv4) address
        - Anything but SubnetMask has to follow the rule that:
        (TokenFromParameter) -band (TokenFromSubnetMask) = (TokenFromScopeId)
        - IPStartRange has to be before IPEndRange
        Implementation for IPv4.
 
    .PARAMETER ScopeId
        String version of ScopeId.
 
    .PARAMETER SubnetMask
        String version of SubnetMask.
 
    .PARAMETER IPStartRange
        String version of StartRange.
 
    .PARAMETER IPEndRange
        String version of EndRange.
 
    .PARAMETER AddressFamily
        AddressFamily that IPs should validate against.
 
    .EXAMPLE
        Assert-ScopeParameter -ScopeId 192.168.1.0 -SubnetMask 255.255.255.0 -IPStartRange 192.168.1.1 -IPEndRange 192.168.1.254 -AddressFamily IPv4
        Validates all parameters against rules and returns nothing (all parameters are correct).
 
    .EXAMPLE
        Assert-ScopeParameter -ScopeId 192.168.1.0 -SubnetMask 255.255.240.0 -IPStartRange 192.168.1.1 -IPEndRange 192.168.1.254 -AddressFamily IPv4
        Returns error informing that using specified SubnetMask with specified ScopeId is incorrect:
        Value of byte 3 in ScopeId (1) is not valid. Binary AND with byte 3 in SubnetMask (240) should be equal to byte 3 in ScopeId (1).
#>


function Assert-ScopeParameter
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ScopeId,

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

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

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('IPv4', 'IPv6')]
        [System.String]
        $AddressFamily
    )

    # Convert the Subnet Mask to be a valid IPAddress
    $netMask = Get-ValidIPAddress -IpString $SubnetMask -AddressFamily $AddressFamily -ParameterName SubnetMask

    # Convert the ScopeID to be a valid IPAddress
    $scope = Get-ValidIPAddress -IpString $ScopeId -AddressFamily $AddressFamily -ParameterName ScopeId

    # Convert the Start Range to be a valid IPAddress
    $startRange = Get-ValidIPAddress -IpString $IPStartRange -AddressFamily $AddressFamily -ParameterName IPStartRange

    # Convert the End Range to be a valid IPAddress
    $endRange = Get-ValidIPAddress -IpString $IPEndRange -AddressFamily $AddressFamily -ParameterName IPEndRange

    # Check to ensure startRange is smaller than endRange
    if ($endRange.Address -lt $startRange.Address)
    {
        $errorMsg = $script:localizedData.InvalidStartAndEndRangeMessage -f $IPStartRange, $IPEndRange
        New-TerminatingError -ErrorId RangeNotCorrect -ErrorMessage $errorMsg -ErrorCategory InvalidArgument
    }

    $addressBytes = @{
        ScopeId      = $scope.GetAddressBytes()
        SubnetMask   = $netMask.GetAddressBytes()
        IPStartRange = $startRange.GetAddressBytes()
        IPEndRange   = $endRange.GetAddressBytes()
    }

    foreach ($parameter in $addressBytes.Keys.Where{ $_ -ne 'SubnetMask' })
    {
        foreach ($ipTokenIndex in 0..3)
        {
            $parameterByte = $addressBytes[$parameter][$ipTokenIndex]
            $subnetMaskByte = $addressBytes['SubnetMask'][$ipTokenIndex]
            $scopeIdByte = $addressBytes['ScopeId'][$ipTokenIndex]
            if (($parameterByte -band $subnetMaskByte) -ne $scopeIdByte)
            {
                $errorMsg = $($script:localizedData.InvalidScopeIdSubnetMask) -f ($ipTokenIndex + 1), $parameter, $parameterByte, $subnetMaskByte, $scopeIdByte

                New-TerminatingError -ErrorId ScopeIdOrMaskIncorrect -ErrorMessage $errorMsg -ErrorCategory InvalidArgument
            }
        }
    }
}
#EndRegion '.\Public\Assert-ScopeParameter.ps1' 113
#Region '.\Public\Get-ValidIPAddress.ps1' -1

<#
    .SYNOPSIS
        Function to convert an IP address from a string to a [System.Net.IPAddress] type.
 
    .DESCRIPTION
        Function to try and convert a IPv4 or IPv6 string to a [System.Net.IPAddress] type.
 
    .PARAMETER IpString
        IpString to convert.
 
    .PARAMETER AddressFamily
        The AddressFamily of the IpString.
 
    .PARAMETER ParameterName
        The ParameterName of the IpString for error purposes.
 
    .OUTPUTS
        [System.Net.IPAddress] object.
#>


function Get-ValidIPAddress
{
    [CmdletBinding()]
    [OutputType([System.Net.IPAddress])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $IpString,

        [Parameter(Mandatory = $true)]
        [ValidateSet('IPv4', 'IPv6')]
        [System.String]
        $AddressFamily,

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

    if ($AddressFamily -eq 'IPv4')
    {
        $ipAddressFamily = 'InterNetwork'
    }
    else
    {
        $ipAddressFamily = 'InterNetworkV6'
    }

    [System.Net.IPAddress] $ipAddress = $null

    $result = [System.Net.IPAddress]::TryParse($IpString, [ref] $ipAddress)

    if (-not $result)
    {
        $errorMsg = $($script:localizedData.InvalidIPAddressFormat) -f $ParameterName

        New-TerminatingError -ErrorId 'NotValidIPAddress' -ErrorMessage $errorMsg -ErrorCategory InvalidType
    }

    if ($ipAddress.AddressFamily -ne $ipAddressFamily)
    {
        $errorMsg = $($script:localizedData.InvalidIPAddressFamily) -f $ipAddress, $AddressFamily

        New-TerminatingError -ErrorId 'InvalidIPAddressFamily' -ErrorMessage $errorMsg -ErrorCategory SyntaxError
    }

    return $ipAddress
}
#EndRegion '.\Public\Get-ValidIPAddress.ps1' 70
#Region '.\Public\Get-ValidTimeSpan.ps1' -1

<#
    .SYNOPSIS
        Validates a string to ensure it is a valid TimeSpan.
 
    .PARAMETER TsString
        The string to validate.
 
    .PARAMETER ParameterName
        The name of the parameter for error purposes.
 
    .OUTPUTS
        [System.TimeSpan] object.
#>


function Get-ValidTimeSpan
{
    [CmdletBinding()]
    [OutputType([System.TimeSpan])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $TsString,

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

    [System.TimeSpan] $timeSpan = New-TimeSpan

    $result = [System.TimeSpan]::TryParse($TsString, [ref] $timeSpan)

    if (-not $result)
    {
        $errorMsg = $($script:localizedData.InvalidTimeSpanFormat) -f $ParameterName

        New-TerminatingError -ErrorId 'NotValidTimeSpan' -ErrorMessage $errorMsg -ErrorCategory InvalidType
    }

    return $timeSpan
}
#EndRegion '.\Public\Get-ValidTimeSpan.ps1' 43
#Region '.\Public\New-TerminatingError.ps1' -1

<#
    .SYNOPSIS
        Creates a terminating error record and throws it.
 
    .PARAMETER ErrorId
        The error ID.
 
    .PARAMETER ErrorMessage
        The error message.
 
    .PARAMETER ErrorCategory
        The error category.
#>


function New-TerminatingError
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ErrorId,

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

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorCategory]
        $ErrorCategory
    )

    $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage
    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $ErrorCategory, $null
    throw $errorRecord
}
#EndRegion '.\Public\New-TerminatingError.ps1' 37
#Region '.\Public\Write-PropertyMessage.ps1' -1

<#
    .SYNOPSIS
        Writes the property message to the verbose stream.
 
    .PARAMETER Parameters
        The parameters to write.
 
    .PARAMETER KeysToSkip
        The keys to skip.
 
    .PARAMETER MessageTemplate
        The message template to use.
#>


function Write-PropertyMessage
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Parameters,

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

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

    foreach ($key in $parameters.keys)
    {
        if ($keysToSkip -notcontains $key)
        {
            $msg = $MessageTemplate -f $key, $parameters[$key]
            Write-Verbose -Message $msg
        }
    }
}
#EndRegion '.\Public\Write-PropertyMessage.ps1' 41