Public/Set-OSDMakeModel.ps1

using namespace System;
using namespace System.Collections.Generic;
using namespace System.Text;

<#
.SYNOPSIS
Sets properties for a Make/Model in the MDT database.

.DESCRIPTION
The Set-OSDMakeModel cmdlet sets properties for a Make/Model in the MDT Database.

Parameters are provided for the most common cases. Other properties can be set via the Settings hashtable, or the Clear list.

Priority is given to the parameters for individual settings, then the -Clear list, then the -Settings table.

It is not possible to update the Make or Model of a Make/Model.

.EXAMPLE
PS C:\> Set-OSDMakeModel $SomeMakeModel -TaskSequence $SomeTaskSequence
Updates the default task sequence for $SomeMakeModel to $SomeTaskSequence.

#>

function Set-OSDMakeModel
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    [OutputType('OSDMakeModel')]
    PARAM(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)][Alias('MakeModel')]
            # A Make/Model object or identity, such as the model name or the object retrieved by Get-OSDMakeModel.
            [MakeModelBinding]$Model,
        [Parameter()][ValidateNotNullOrEmpty()]
            # A task sequence, either a string ID or one retrieved by Get-OSDTaskSequence.
            [TaskSequenceBinding]$TaskSequence,
        [Parameter()][ValidateNotNullOrEmpty()]
            # A Driver Group for the make/model, usually specified alongside -TaskSequence when the operating system has changed.
            [string]$DriverGroup,
        [Parameter()][ValidateNotNull()]
            # A list of field names to be emptied.
            [string[]]$Clear = @(),
        [Parameter()][ValidateNotNull()]
            # A table of other values to set.
            [hashtable]$Settings = @{},
        [Parameter()]
            # Re-fetch the make/model modified and spit it out.
            [switch]$PassThru
    )

    begin
    {
        Assert-OSDConnected

        [hashset[string]]$IllegalColumns = [HashSet[string]]::new(([string[]]@('Make', 'Model')), [StringComparer]::OrdinalIgnoreCase)
        [Dictionary[string, psobject]]$ValidColumns = [Dictionary[string, psobject]]::new([StringComparer]::OrdinalIgnoreCase)
        [psobject[]]$RawColumnData = @(Invoke-SQLQuery -Query "select COLUMN_NAME as ColumnName, IS_NULLABLE as IsNullable, DATA_TYPE as DataType, CHARACTER_MAXIMUM_LENGTH as MaxLength from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Settings' or TABLE_NAME = 'MakeModelIdentity'" -Property 'ColumnName', 'ColumnDefault', 'IsNullable', 'DataType', 'MaxLength')
        foreach($Column in $RawColumnData)
        {
            if($Column.MaxLength -eq [DBNull]::Value)
            {
                $Column.MaxLength = 0
            }
            $ValidColumns[$Column.ColumnName] = [psobject]@{
                IsNullable = $Column.IsNullable -eq 'YES'
                DataType = switch($Column.DataType)
                {
                    'int' { [int] }
                    default { [string] }
                }
                MaxLength = [int]$Column.MaxLength
            }
        }
    }

    process
    {
        [OSDMakeModel]$MakeModelObject = Resolve-MakeModelBinding -Bindings $Model
        [HashSet[string]]$SetToDBNull = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)

        # Clean up our aliases
        if($Settings.ContainsKey('TaskSequence'))
        {
            $Value = $Settings['TaskSequence']
            $Settings.Remove('TaskSequence')
            Write-Verbose 'Removed TaskSequence from Settings.'
            if(!$Settings.ContainsKey('TaskSequenceID'))
            {
                $Settings['TaskSequenceID'] = $Value
                Write-Verbose 'Used TaskSequence value to fill TaskSequenceID'
            }
        }

        # Validate Settings
        foreach($Key in $Settings.Keys)
        {
            if(!$ValidColumns.ContainsKey($Key) -or $IllegalColumns.Contains($Key))
            {
                throw [ArgumentException]::new("Illegal column name ""$Key"" for table ""Settings"".", 'Settings');
            } else
            {
                if($null -eq $Settings[$Key])
                {
                    if(!$ValidColumns[$Key].IsNullable)
                    {
                        throw [ArgumentException]::new("Illegal null value for ""$Key"" in table ""Settings"".", 'Settings');
                    } else
                    {
                        [void]$SetToDBNull.Add($Key)
                    }
                }
                if($Settings[$Key] -isnot $ValidColumns[$Key].DataType)
                {
                    Write-Verbose "Warning: Type ""$($Settings[$Key].GetType().FullName)"" of value for key ""$Key"" does not match type ""$($ValidColumns[$Key].DataType.FullName)"" detected from table ""Settings""."
                }
            }
        }

        # Validate Clear
        if($null -ne $Clear -and $Clear.Count -gt 0)
        {
            [HashSet[string]]$ClearSet = [HashSet[string]]::new($Clear, [StringComparer]::OrdinalIgnoreCase)

            # Clean up aliases
            if($ClearSet.Contains('TaskSequence'))
            {
                [void]$ClearSet.Remove('TaskSequence')
                [void]$ClearSet.Add('TaskSequenceID')
                Write-Verbose 'Removed TaskSequence from Clear, added TaskSequenceID'
            }

            # Validate/Merge Clear into Settings
            foreach($Key in $Clear)
            {
                if(!$ValidColumns.ContainsKey($Key) -or $IllegalColumns.Contains($Key))
                {
                    throw [ArgumentException]::new("Illegal column name ""$Key"" for table ""Settings"".", 'Clear');
                } else
                {
                    if($ValidColumns[$Key].IsNullable)
                    {
                        [void]$SetToDBNull.Add($Key)
                        $Settings[$Key] = $null
                        Write-Verbose "Merged Clear key ""$Key"" into settings with value null."
                    } else
                    {
                        $Settings[$Key] = [string]::Empty
                        Write-Verbose "Merged Clear key ""$Key"" into settings with value ''."
                    }
                }
            }
        }

        # Now process settings from bound parameters.
        if($PSBoundParameters.ContainsKey('TaskSequence'))
        {
            $Settings['TaskSequenceID'] = (Resolve-TaskSequenceBinding -Bindings $TaskSequence).ID
            [void]$SetToDBNull.Remove('TaskSequenceID')
            Write-Verbose "Used value of TaskSequence parameter to set TaskSequenceID."
        }

        if($PSBoundParameters.ContainsKey('DriverGroup'))
        {
            $Settings['DriverGroup'] = $DriverGroup
            [void]$SetToDBNull.Remove('DriverGroup')
            Write-Verbose "Used value of DriverGroup parameter to set DriverGroup."
        }

        # Now that we have everything processed, validate data types for known values.
        if($Settings.ContainsKey('TaskSequenceID') -and ![string]::IsNullOrEmpty($Settings['TaskSequenceID']))
        {
            $Value = Resolve-TaskSequenceBinding -Bindings $Settings['TaskSequenceID'] -ErrorAction Stop
            $Settings['TaskSequenceID'] = $Value.ID
            Write-Verbose "Validated TaskSequenceID to ""$($Settings['TaskSequenceID'])""."
        }

        # Some values need to be set to DBNull
        foreach($Key in $SetToDBNull)
        {
            $Settings[$Key] = [DBNull]::Value
            Write-Verbose "Set ""$Key"" from null to DBNull."
        }

        # Everything in MakeModelIdentity is in the IllegalKey set, so we don't need to update it like we do in Set-OSDComputer
        # Now, set values in the database
        if($PSCmdlet.ShouldProcess($ComputerObject.ComputerName, "Update the computer's settings."))
        {
            # Update the settings table.
            if($Settings.Count -gt 0)
            {
                [hashtable]$Parameters = @{ '@ID' = $MakeModelObject.InternalID }
                [string]$Query = 'UPDATE Settings SET ' + [string]::Join(', ', (
                    $Settings.Keys | ForEach-Object {
                        $Parameters["@$_"] = $Settings[$_]
                        "$_ = @$_"
                    }
                )) + ' WHERE ID = @ID AND TYPE = ''M'''
                $null = Invoke-SQLScalar -Query $Query -Parameters $Parameters
            }

            if($PassThru)
            {
                Write-Output -InputObject (Get-OSDMakeModel -InternalID $MakeModelObject.InternalID)
            }
        }
    }
}