Modules/SB.Util/SB.Util.psm1

<#
    .SYNOPSIS
        Tests if a given object contains a property.
 
    .PARAMETER Object
        Object to test.
 
    .PARAMETER PropertyName
        The name of the property to check.
 
    .EXAMPLE
        $object = [pscustomobject] @{
            TestProperty = "TestValue"
        }
        Test-SBDscObjectHasProperty -Object $object -PropertyName 'TestProperty'
#>

function Test-SBDscObjectHasProperty
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true,Position=1)]
        [object]
        $Object,

        [parameter(Mandatory = $true,Position=2)]
        [string]
        $PropertyName
    )

    if (([bool]($Object.PSobject.Properties.name -contains $PropertyName)) -eq $true)
    {
        if ($null -ne $Object.$PropertyName)
        {
            return $true
        }
    }
    return $false
}

<#
    .SYNOPSIS
        Compares a set of current values with desired values based on named keys to check.
 
    .PARAMETER CurrentValues
        A hashtable of current values.
 
    .PARAMETER DesiredValues
        A hashtable of desired values or a CimInstance object.
 
    .PARAMETER ValuesToCheck
        An array of key names to check between the current and desired values.
 
    .EXAMPLE
        $params = @{
            CurrentValues = @{
                Key1 = "value1"
                Key2 = "value2"
            }
            DesiredValues = = @{
                Key1 = "value1"
                Key2 = "value3"
            }
            ValuesToCheck = @(
                "Key1"
            )
        }
        # returns true
        Test-SBParameterState @params
 
    .EXAMPLE
        $params = @{
            CurrentValues = @{
                Key1 = "value1"
                Key2 = "value2"
            }
            DesiredValues = = @{
                Key1 = "value1"
                Key2 = "value3"
            }
        }
        # returns false
        Test-SBParameterState @params
 
    .EXAMPLE
        $cimInstance = Get-CimInstance -ClassName WIN32_ComputerSystem
        $params = @{
            CurrentValues = @{
                Domain = "contoso.com"
            }
            DesiredValues = $cimInstance
        }
        # returns true if $cimInstance.Domain is 'contoso.com'
        Test-SBParameterState @params
#>

function Test-SBParameterState()
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [parameter(Mandatory = $true, Position=1)]
        [hashtable]
        $CurrentValues,

        [parameter(Mandatory = $true, Position=2)]
        [object]
        $DesiredValues,

        [parameter(Mandatory = $false, Position=3)]
        [array]
        $ValuesToCheck
    )

    $returnValue = $true

    if (($DesiredValues.GetType().Name -ne "HashTable") `
        -and ($DesiredValues.GetType().Name -ne "CimInstance") `
        -and ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary"))
    {
        throw ("Property 'DesiredValues' in Test-SBParameterState must be either a " + `
               "Hashtable or CimInstance. Type detected was $($DesiredValues.GetType().Name)")
    }

    if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck))
    {
        throw ("If 'DesiredValues' is a Hashtable then property 'ValuesToCheck' must contain " + `
               "a value")
    }

    if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1))
    {
        $KeyList = $DesiredValues.Keys
    }
    else
    {
        $KeyList = $ValuesToCheck
    }

    $KeyList | ForEach-Object -Process {
        if ($_ -ne "Verbose")
        {
            if (($CurrentValues.ContainsKey($_) -eq $false) `
            -or ($CurrentValues.$_ -ne $DesiredValues.$_) `
            -or (($DesiredValues.ContainsKey($_) -eq $true) -and ($DesiredValues.$_.GetType().IsArray)))
            {
                if ($DesiredValues.GetType().Name -eq "HashTable" -or `
                    $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary")
                {

                    $CheckDesiredValue = $DesiredValues.ContainsKey($_)
                }
                else
                {
                    $CheckDesiredValue = Test-SBDscObjectHasProperty $DesiredValues $_
                }

                if ($CheckDesiredValue)
                {
                    $desiredType = $DesiredValues.$_.GetType()
                    $fieldName = $_
                    if ($desiredType.IsArray -eq $true)
                    {
                        if (($CurrentValues.ContainsKey($fieldName) -eq $false) `
                        -or ($null -eq $CurrentValues.$fieldName))
                        {
                            Write-Verbose -Message ("Expected to find an array value for " + `
                                                    "property $fieldName in the current " + `
                                                    "values, but it was either not present or " + `
                                                    "was null. This has caused the test method " + `
                                                    "to return false.")
                            $returnValue = $false
                        }
                        else
                        {
                            $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName `
                                                           -DifferenceObject $DesiredValues.$fieldName
                            if ($null -ne $arrayCompare)
                            {
                                Write-Verbose -Message ("Found an array for property $fieldName " + `
                                                        "in the current values, but this array " + `
                                                        "does not match the desired state. " + `
                                                        "Details of the changes are below.")
                                $arrayCompare | ForEach-Object -Process {
                                    Write-Verbose -Message "$($_.InputObject) - $($_.SideIndicator)"
                                }
                                $returnValue = $false
                            }
                        }
                    }
                    else
                    {
                        switch ($desiredType.Name)
                        {
                            "String" {
                                if ([string]::IsNullOrEmpty($CurrentValues.$fieldName) `
                                -and [string]::IsNullOrEmpty($DesiredValues.$fieldName))
                                {}
                                else
                                {
                                    Write-Verbose -Message ("String value for property " + `
                                                            "$fieldName does not match. " + `
                                                            "Current state is " + `
                                                            "'$($CurrentValues.$fieldName)' " + `
                                                            "and desired state is " + `
                                                            "'$($DesiredValues.$fieldName)'")
                                    $returnValue = $false
                                }
                            }
                            "Int32" {
                                if (($DesiredValues.$fieldName -eq 0) `
                                -and ($null -eq $CurrentValues.$fieldName))
                                {}
                                else
                                {
                                    Write-Verbose -Message ("Int32 value for property " + `
                                                            "$fieldName does not match. " + `
                                                            "Current state is " + `
                                                            "'$($CurrentValues.$fieldName)' " + `
                                                            "and desired state is " + `
                                                            "'$($DesiredValues.$fieldName)'")
                                    $returnValue = $false
                                }
                            }
                            "Int16" {
                                if (($DesiredValues.$fieldName -eq 0) `
                                -and ($null -eq $CurrentValues.$fieldName))
                                {}
                                else
                                {
                                    Write-Verbose -Message ("Int16 value for property " + `
                                                            "$fieldName does not match. " + `
                                                            "Current state is " + `
                                                            "'$($CurrentValues.$fieldName)' " + `
                                                            "and desired state is " + `
                                                            "'$($DesiredValues.$fieldName)'")
                                    $returnValue = $false
                                }
                            }
                            default {
                                Write-Verbose -Message ("Unable to compare property $fieldName " + `
                                                        "as the type ($($desiredType.Name)) is " + `
                                                        "not handled by the " + `
                                                        "Test-SBParameterState cmdlet")
                                $returnValue = $false
                            }
                        }
                    }
                }
            }
        }
    }
    return $returnValue
}

<#
    .SYNOPSIS
        Converts a secure string to plaintext.
 
    .PARAMETER SecureString
        SecureString object
 
    .EXAMPLE
        $secureString = ConvertTo-SecureString -String "string" -AsPlainText -Force
        ConvertTo-PlainText -SecureString $secureString
#>

function ConvertTo-PlainText
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [securestring]
        $SecureString
    )
    process
    {
        $BTSR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
        return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BTSR)
    }
}

<#
    .SYNOPSIS
        Creates a new SQL connection string from the inputs.
 
    .PARAMETER DataSource
        The SQL server / instance name.
 
    .PARAMETER InitialCatalog
        The SQL database name.
 
    .PARAMETER IntegratedSecurity
        Type of security for the SQL connection string.
 
    .PARAMETER Credential
        Username and password for the connection string. Not needed if IntegratedSecurity is True or SSPI.
 
    .PARAMETER Encrypt
        Indicates whether the connection string will be set up to use SSL/TLS.
 
    .EXAMPLE
        $params = @{
            DataSource = 'SQL01.contoso.com'
            InitialCatalog = 'MyDatabase'
            IntegratedSecurity = 'SSPI'
        }
        # returns 'Data Source=SQL01.contoso.com;Intial Catalog=MyDatabase;IntegratedSecurity=SSPI'
        New-SqlConnectionString @params
 
    .EXAMPLE
        $credential = Get-Credential
        $params = @{
            DataSource = 'SQL01.contoso.com'
            InitialCatalog = 'MyDatabase'
            IntegratedSecurity = 'False'
            Credential = $credential
            Encrypt = $true
        }
        # returns 'Data Source=SQL01.contoso.com;Intial Catalog=MyDatabase;IntegratedSecurity=SSPI;`" +`
        # 'User Name=<username>;Password=<password>'
        New-SqlConnectionString @params
#>

function New-SqlConnectionString
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $DataSource,

        [Parameter(Mandatory = $true)]
        [string]
        $InitialCatalog,

        [Parameter(Mandatory = $true)]
        [ValidateSet('True','False','SSPI')]
        $IntegratedSecurity,

        [Parameter()]
        [pscredential]
        $Credential,

        [Parameter()]
        [switch]
        $Encrypt
    )
    process
    {
        $sqlConnectionStringBuilder = New-Object -TypeName System.Data.Common.DbConnectionStringBuilder
        $sqlConnectionStringBuilder["Data Source"] = $DataSource
        $sqlConnectionStringBuilder["Initial Catalog"] = $InitialCatalog
        $sqlConnectionStringBuilder["Integrated Security"] = 'False'
        switch ($IntegratedSecurity)
        {
            'True' { $sqlConnectionStringBuilder["Integrated Security"] = 'True' }
            'False' { $sqlConnectionStringBuilder["Integrated Security"] = 'False' }
            'SSPI' { $sqlConnectionStringBuilder['Integrated Security'] = 'SSPI' }
        }
        if ($Credential)
        {
            $sqlConnectionStringBuilder.UserID = $Credential.UserName
            $sqlConnectionStringBuilder.Password = (ConvertTo-PlainText -SecureString $Credential.Password)
        }
        $sqlConnectionStringBuilder.Encrypt = $false
        if ($Encrypt.IsPresent)
        {
            $sqlConnectionStringBuilder.Encrypt = $true
        }
        $connectionString = $sqlConnectionStringBuilder.ConnectionString
        if ($IntegratedSecurity -eq 'SSPI')
        {
            $connectionString = $connectionString.Replace('Integrated Security=True','Integrated Security=SSPI')
        }
        return $sqlConnectionStringBuilder.ConnectionString.Replace("UserID","User Id")
    }
}

<#
    .SYNOPSIS
        Retrieves a given property value from a SQL connection string.
 
    .PARAMETER SqlConnectionString
        A SQL connection string.
 
    .PARAMETER PropertyName
        The property name whose value to retrieve.
 
    .EXAMPLE
        $connectionString = "Data Source=SQL1;Initial Catalog=MyDatabase;Integrated Security=SSPI"
        # returns SQL1
        Get-SqlConnectionStringPropertyValue -SqlConnectionString $connectionString -PropertyName "Data Source"
#>

function Get-SqlConnectionStringPropertyValue
{
    [CmdletBinding()]
    [OutputType([object])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $SqlConnectionString,

        [Parameter(Mandatory = $true)]
        [string]
        $PropertyName
    )
    process
    {
        $params = @{
            TypeName     = 'System.Data.SqlClient.SqlConnectionStringBuilder'
            ArgumentList = $SqlConnectionString
        }
        $sqlConnectionStringBuilder = New-Object @params
        if ($PropertyName -eq 'Integrated Security' -and
            $SqlConnectionString.Contains('Integrated Security=SSPI'))
        {
            return 'SSPI'
        }
        return $sqlConnectionStringBuilder[$PropertyName]
    }
}

<#
    .SYNOPSIS
        Compares two secure strings, returns true if they are the same, false otherwise.
 
    .PARAMETER ReferenceSecureString
        The first secure string.
 
    .PARAMETER DifferenceSecureString
        The second secure string to compare to.
 
    .EXAMPLE
        $secureString1 = ConvertTo-SecureString -String "string1" -AsPlainText -Force
        $secureString2 = ConvertTo-SecureString -String "string2" -AsPlainText -Force
        # returns false
        Compare-SecureString -ReferenceSecureString $secureString1 -DifferenceSecureString $secureString2
#>

function Compare-SecureString
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [securestring]
        $ReferenceSecureString,

        [Parameter(Mandatory = $true)]
        [securestring]
        $DifferenceSecureString
    )
    process
    {
        return ((ConvertTo-PlainText -SecureString $ReferenceSecureString) -eq
                (ConvertTo-PlainText -SecureString $DifferenceSecureString))
    }
}

<#
    .SYNOPSIS
        Gets the account name without the domain prefix or suffix.
 
    .PARAMETER FullAccountNameWithDomain
        An account in the format CORP\account or account@contoso.com
 
    .EXAMPLE
        # returns 'account'
        Get-AccountName -FullAccountNameWithDomain 'CORP\account'
#>

function Get-AccountName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $FullAccountNameWithDomain
    )
    process
    {
        if (($FullAccountNameWithDomain.IndexOf('\')) -gt 0)
        {
            $array = $FullAccountNameWithDomain.Split('\', [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($null -ne $array -and $array.Length -gt 0)
            {
                return $array[1]
            }
        }
        else
        {
            $array = $FullAccountNameWithDomain.Split('@', [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($null -ne $array -and $array.Length -gt 0)
            {
                return $array[0]
            }
        }
    }
}

<#
    .SYNOPSIS
        Retrieves the domain name from a full account name.
 
    .PARAMETER FullAccountWithDomain
        An account in the format CORP\account or account@contoso.com
 
    .EXAMPLE
        # returns 'CORP'
        Get-AccountDomainName -FullAccountNameWithDomain 'CORP\account'
#>

function Get-AccountDomainName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $FullAccountNameWithDomain
    )
    process
    {
        if (($FullAccountNameWithDomain.IndexOf('\')) -gt 0)
        {
            $array = $FullAccountNameWithDomain.Split('\', [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($null -ne $array -and $array.Length -gt 0)
            {
                if ($array.Length -eq 2)
                {
                    return $array[0]
                }
            }
        }
        else
        {
            $array = $FullAccountNameWithDomain.Split('@', [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($null -ne $array -and $array.Length -gt 0)
            {
                if ($array.Length -eq 2)
                {
                    return $array[1]
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Gets the distinguished name for a domain.
 
    .PARAMETER DomainName
        A domain name like CONTOSO
 
    .EXAMPLE
        # returns ex. 'DC=contoso,DC=com'
        Get-DistinguishedNameForDomain -DomainName 'CONTOSO'
#>

function Get-DistinguishedNameForDomain
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $DomainName
    )
    process
    {
        return ([adsi]"LDAP://$domainName").distinguishedName
    }
}

<#
    .SYNOPSIS
        Fully qualifies a domain name
 
    .PARAMETER DomainName
        The domain name like CONTOSO
 
    .EXAMPLE
        # returns ex. contoso.com
        Get-FullyQualifiedDomainName -DomainName "CONTOSO"
#>

function Get-FullyQualifiedDomainName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $DomainName
    )
    process
    {
        $distinguishedName = Get-DistinguishedNameForDomain -DomainName $DomainName
        $resultArray = @()
        $componentArray = $distinguishedName.Split(',',
                                                   [System.StringSplitOptions]::RemoveEmptyEntries)
        foreach ($component in $componentArray)
        {
            $componentKeyValuePairArray = $component.Split('=',
                                                           [System.StringSplitOptions]::RemoveEmptyEntries)
            if ($componentKeyValuePairArray.Length -eq 2 -and
                [string]::Equals($componentKeyValuePairArray[0],
                                 'DC',
                                 [System.StringComparison]::OrdinalIgnoreCase))
            {
                $resultArray += $componentKeyValuePairArray[1]
            }
        }
        return ($resultArray -join '.')
    }
}

<#
    .SYNOPSIS
        Gets the NetBIOS format of a domain name
 
    .PARAMETER DomainName
        The domain name like contoso.com
 
    .EXAMPLE
        # returns ex. 'CONTOSO'
        Get-NetBIOSDomainName -DomainName 'contoso.com'
#>

function Get-NetBIOSDomainName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $DomainName
    )
    process
    {
        return ([adsi]"LDAP://$domainName").name.ToUpper()
    }
}

<#
    .SYNOPSIS
        Formats an account to a given format.
 
    .PARAMETER FullAccountNameWithDomain
        Full account with domain name e.g. CORP\account or account@contoso.com
 
    .PARAMETER Format
        The format to take the account to.
 
    .EXAMPLE
        # returns ex. account@contoso.com
        Format-AccountName -FullAccountNameWithDomain 'CONTOSO\account' -Format 'UserLogonName'
#>

function Format-AccountName
{
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $FullAccountNameWithDomain,

        [Parameter(Mandatory = $true)]
        [ValidateSet('UserLogonName',
                     'UserLogonNamePreWindows2000')]
        [string]
        $Format
    )
    process
    {
        if ($Format -eq 'UserLogonName')
        {
            $stringFormat = '{0}@{1}'
            $accountName = Get-AccountName -FullAccountNameWithDomain $FullAccountNameWithDomain
            $domainName = Get-AccountDomainName -FullAccountNameWithDomain $FullAccountNameWithDomain
            $fullyQualifiedDomainName = Get-FullyQualifiedDomainName -DomainName $domainName
            return ([string]::Format($stringFormat, $accountName, $fullyQualifiedDomainName))
        }
        if ($Format -eq 'UserLogonNamePreWindows2000')
        {
            $stringFormat = '{0}\{1}'
            $accountName = Get-AccountName -FullAccountNameWithDomain $FullAccountNameWithDomain
            $domainName = Get-AccountDomainName -FullAccountNameWithDomain $FullAccountNameWithDomain
            $netBIOSDomainName = Get-NetBIOSDomainName -DomainName $domainName
            return ([string]::Format($stringFormat, $netBIOSDomainName, $accountName))
        }
    }
}

<#
    .SYNOPSIS
        Compares two account names regardless of their formatting
 
    .PARAMETER ReferenceAccountNameWithDomain
        Reference account e.g. 'CONTOSO\account'
 
    .PARAMETER DifferenceAccountNameWithDomain
        Difference account e.g. 'account@contoso.com'
 
    .EXAMPLE
        # returns true
        Compare-AccountName -ReferenceAccountWithDomain 'CONTOSO\account'`
                            -DifferenceAccountWithDomain 'account@contoso.com
#>

function Compare-AccountName
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ReferenceAccountNameWithDomain,

        [Parameter(Mandatory = $true)]
        [string]
        $DifferenceAccountNameWithDomain
    )
    process
    {
        $reference = (Format-AccountName -FullAccountNameWithDomain $ReferenceAccountNameWithDomain -Format UserLogonNamePreWindows2000).ToLower()
        $difference = (Format-AccountName -FullAccountNameWithDomain $DifferenceAccountNameWithDomain -Format UserLogonNamePreWindows2000).ToLower()
        return ($reference -eq $difference)
    }
}

Export-ModuleMember -Function *