Cofl.OSDManagement.psm1

# This is a set of cmdlets for the management of the MDT and WDS databases (MSSQL & AD).

using namespace System.Diagnostics.CodeAnalysis;
using namespace System.Collections;
using namespace System.Collections.Generic;
using namespace System.Data;
using namespace System.Data.SqlClient;
using namespace System.Net.NetworkInformation;
using namespace System.Management.Automation;

# Internal globals
[Nullable[bool]]$Script:OSDIsConnected = $false
[string]$Script:OSDScriptsMDTRoot = $null
[string]$Script:OSDScriptsSQLConnectString = $null
[string]$Script:OSDComputerNameTemplate = [string]::Empty
[SqlConnection]$Script:OSDScriptsSQLConnection = $null
[string[]]$Script:CacheTaskSequenceID = @()
[string[]]$Script:CacheTaskSequenceGroups = @()
[string[]]$Script:CacheDriverGroups = @()
[string[]]$Script:CacheMakes = @()
[string[]]$Script:CacheModels = @()

function Get-Config
{
    [CmdletBinding()] PARAM ()
    Import-Configuration -CompanyName 'Cofl' -Name 'OSDManagement'
}

function Assert-OSDConnected
{
    if(!$Script:OSDIsConnected)
    {
        throw [OSDNotConnectedException]::new()
    }
}

function Format-MacAddressForMDT
{
    PARAM (
        [Parameter(Mandatory = $true, Position = 0)][PhysicalAddress]$MacAddress
    )

    if($null -eq $MacAddress)
    {
        return [string]::Empty
    }
    return [string]::Format('{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}', [object[]]$MacAddress.GetAddressBytes())
}

function Format-GuidForLDAPFilter
{
    [CmdletBinding(DefaultParameterSetName='FromGuid')]
    PARAM (
        [Parameter(ParameterSetName='FromGuid')][Nullable[Guid]]$Guid = $null,
        [Parameter(ParameterSetName='FromMacAddress')][MacAddressBinding]$MacAddress = $null
    )

    if($null -ne $MacAddress.Address)
    {
        return [string]::Format('\00\00\00\00\00\00\00\00\00\00\{0:x2}\{1:x2}\{2:x2}\{3:x2}\{4:x2}\{5:x2}', [object[]]$MacAddress.Address.GetAddressBytes())
    } elseif($null -ne $Guid)
    {
        return [string]::Format('\{0:x2}\{1:x2}\{2:x2}\{3:x2}\{4:x2}\{5:x2}\{6:x2}\{7:x2}\{8:x2}\{9:x2}\{10:x2}\{11:x2}\{12:x2}\{13:x2}\{14:x2}\{15:x2}', [object[]]$Guid.ToByteArray())
    } else
    {
        return [string]::Empty
    }
}

function Get-DefaultComputerName
{
    PARAM (
        [Parameter(Mandatory = $true, Position = 0)][int]$AssetTag
    )

    return $Script:OSDComputerNameTemplate -f $AssetTag
}

# Cleanup
function Set-OnRemove {
    [SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Only used internally.')]
    [CmdletBinding()]
    PARAM (
    )

    $PSCmdlet.MyInvocation.MyCommand.Module.OnRemove = {
        Disconnect-OSD
    }
}
Set-OnRemove

# Import classes
Get-ChildItem -Path $PSScriptRoot\Classes\*.ps1 -File | ForEach-Object {
    . $_.FullName
}
$Accelerator = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
$Accelerator::Remove('OSDComputer')
$Accelerator::Remove('OSDMakeModel')
$Accelerator::Remove('OSDTaskSequence')
$Accelerator::Remove('OSDTaskSequenceGroup')
$Accelerator::Remove('MacAddressBinding')
$Accelerator::Remove('TaskSequenceBinding')
$Accelerator::Remove('OSDComputerBinding')
$Accelerator::Add('OSDComputer', [OSDComputer])
$Accelerator::Add('OSDMakeModel', [OSDMakeModel])
$Accelerator::Add('OSDTaskSequence', [OSDTaskSequence])
$Accelerator::Add('OSDTaskSequenceGroup', [OSDTaskSequenceGroup])
$Accelerator::Add('MacAddressBinding', [MacAddressBinding])
$Accelerator::Add('TaskSequenceBinding', [TaskSequenceBinding])
$Accelerator::Add('OSDComputerBinding', [OSDComputerBinding])

# Declare private functions
function Invoke-SQLQuery
{
    [CmdletBinding(DefaultParameterSetName = 'AsDictionary')]
    PARAM
    (
        [Parameter(Mandatory = $true, ParameterSetName = 'AsDictionary')]
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
            [ValidateNotNullOrEmpty()][string]$Query,
        [Parameter(ParameterSetName = 'AsDictionary')]
        [Parameter(ParameterSetName = 'Properties')]
            [ValidateNotNullOrEmpty()][hashtable]$Parameters,
        [Parameter(Mandatory = $true, ParameterSetName = 'AsDictionary')]
            [switch]$AsDictionary,
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
            [ValidateNotNullOrEmpty()][string[]]$Property
    )

    Assert-OSDConnected

    try
    {
        [SqlCommand]$Command = [SqlCommand]::new($Query, $Script:OSDScriptsSQLConnection)
        if($null -ne $Parameters -and $Parameters.Count -gt 0)
        {
            foreach($Key in $Parameters.Keys)
            {
                if($null -eq $Parameters[$Key])
                {
                    [void]$Command.Parameters.AddWithValue($Key, [DBNull]::Value)
                } else
                {
                    [void]$Command.Parameters.AddWithValue($Key, $Parameters[$Key])
                }
            }
        }

        [SqlDataAdapter]$Adapter = [SqlDataAdapter]::new($Command)
        [DataSet]$DataSet = [DataSet]::new()
        [void]$Adapter.Fill($DataSet)
        foreach($Table in $DataSet.Tables)
        {
            if($null -eq $Table.Rows)
            {
                continue
            }
            foreach($Row in $Table.Rows)
            {
                if($AsDictionary)
                {
                    [Dictionary[string, object]]$Dictionary = [Dictionary[string, object]]::new()
                    foreach($Column in $Table.Columns)
                    {
                        $Dictionary[$Column.ColumnName] = $Row[$Column]
                    }
                    Write-Output -InputObject $Dictionary -NoEnumerate
                } else
                {
                    if($Property.Count -eq 1)
                    {
                        Write-Output -InputObject $Row[$Property] -NoEnumerate
                    } else
                    {
                        Write-Output -InputObject ($Row | Select-Object -Property $Property) -NoEnumerate
                    }
                }
            }
        }
    } finally
    {
        if($null -ne $DataSet)
        {
            $DataSet.Dispose()
        }

        if($null -ne $Adapter)
        {
            $Adapter.Dispose()
        }

        if($null -ne $Command)
        {
            $Command.Dispose()
        }
    }
}

function Invoke-SQLScalar
{
    [CmdletBinding()]
    PARAM (
        [Parameter(Mandatory = $true)][ValidateNotNull()][string]$Query,
        [Parameter()][ValidateNotNullOrEmpty()][hashtable]$Parameters
    )

    Assert-OSDConnected

    try
    {
        [SqlCommand]$Command = [SqlCommand]::new($Query, $Script:OSDScriptsSQLConnection)
        if($null -ne $Parameters -and $Parameters.Count -gt 0)
        {
            foreach($Key in $Parameters.Keys)
            {
                if($null -eq $Parameters[$Key])
                {
                    [void]$Command.Parameters.AddWithValue($Key, [DBNull]::Value)
                } else
                {
                    [void]$Command.Parameters.AddWithValue($Key, $Parameters[$Key])
                }
            }
        }
        Write-Output $Command.ExecuteScalar()
    } finally
    {
        if ($Command)
        {
            $Command.Dispose()
        }
    }
}

# Import public functions
Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -File | ForEach-Object {
    . $_.FullName
}

####
## Completers
####

function TaskSequenceIDCompleter {
    PARAM ($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameter)

    $Script:CacheTaskSequenceID.Where{ $_ -ilike "$WordToComplete*" }.ForEach{
        $Fill = if($_.Contains(' ')){"'$_'"} else { $_ }
        [CompletionResult]::New($Fill, $_, 'ParameterValue', $_)
    }
}
Register-ArgumentCompleter -CommandName Get-OSDTaskSequence -ParameterName ID -ScriptBlock $Function:TaskSequenceIDCompleter
Register-ArgumentCompleter -CommandName New-OSDMakeModel -ParameterName TaskSequence -ScriptBlock $Function:TaskSequenceIDCompleter
Register-ArgumentCompleter -CommandName Set-OSDComputer -ParameterName TaskSequence -ScriptBlock $Function:TaskSequenceIDCompleter
Register-ArgumentCompleter -CommandName Set-OSDMakeModel -ParameterName TaskSequence -ScriptBlock $Function:TaskSequenceIDCompleter
Register-ArgumentCompleter -CommandName Invoke-ReimageComputer -ParameterName TaskSequence -ScriptBlock $Function:TaskSequenceIDCompleter

function TaskSequenceGroupCompleter {
    PARAM ($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameter)

    $Script:CacheTaskSequenceGroups.Where({ $_ -ilike "$WordToComplete*" }).ForEach({
        $Fill = if($_.Contains(' ')){"'$_'"} else {$_}
        [CompletionResult]::New($Fill, $_, 'ParameterValue', $_)
    })
}
Register-ArgumentCompleter -CommandName Get-OSDTaskSequence -ParameterName Group -ScriptBlock $Function:TaskSequenceGroupCompleter

function DriverGroupsCompleter {
    PARAM ($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameter)

    $Script:CacheDriverGroups.Where({ $_ -ilike "$WordToComplete*" }).ForEach({
        $Fill = if($_.Contains(' ')){"'$_'"} else { $_ }
        [CompletionResult]::New($Fill, $_, 'ParameterValue', $_)
    })
}
Register-ArgumentCompleter -CommandName New-OSDMakeModel -ParameterName DriverGroup -ScriptBlock $Function:DriverGroupsCompleter
Register-ArgumentCompleter -CommandName Set-OSDComputer -ParameterName DriverGroup -ScriptBlock $Function:DriverGroupsCompleter
Register-ArgumentCompleter -CommandName Set-OSDMakeModel -ParameterName DriverGroup -ScriptBlock $Function:DriverGroupsCompleter

function MakeCompleter {
    PARAM ($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameter)

    $Script:CacheMakes.Where({ $_ -ilike "$WordToComplete*" }).ForEach({
        $Fill = if($_.Contains(' ')){"'$_'"} else { $_ }
        [CompletionResult]::New($Fill, $_, 'ParameterValue', $_)
    })
}
Register-ArgumentCompleter -CommandName Get-OSDMakeModel -ParameterName Make -ScriptBlock $Function:MakeCompleter


function ModelCompleter {
    PARAM ($CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameter)


    $Script:CacheModels.Where({ $_ -ilike "$WordToComplete*" }).ForEach({
        $Fill = if($_.Contains(' ')){"'$_'"} else { $_ }
        [CompletionResult]::New($Fill, $_, 'ParameterValue', $_)
    })
}
Register-ArgumentCompleter -CommandName Get-OSDMakeModel -ParameterName Model -ScriptBlock $Function:ModelCompleter
Register-ArgumentCompleter -CommandName Set-OSDMakeModel -ParameterName Model -ScriptBlock $Function:ModelCompleter
Register-ArgumentCompleter -CommandName Remove-OSDMakeModel -ParameterName Model -ScriptBlock $Function:ModelCompleter

# Do an initial initialization once we've got all the above done.
if((Get-Config).AutoConnectOnImport)
{
    Connect-OSD -UseConfiguredPath
}