Import PowerShell scripts into Configuration Manager

Inception spinning top

Update: It was pointed out that the new script function required PowerShell v5 to use the New-GUID cmdlet. I have updated the function to use [guid]::NewGuid() so people do not have to upgrade the WMF, even though they should.

+You can create a script within a script to import the inner script into ConfigMgr. I think of it like a script “Inception”.

Although most environments won’t need to import the same script multiple times, sometimes I find customers that do. It also is nice to be able to provide one script to my customers that imports multiple scripts for them.

In cases where you want validation information on your parameters like available values and a default value, this function handles that.

Apologetics: There are two places in my function that I had to dump out to a temp file, which I’d like to avoid, but have not found a way around yet.

The logic

Two functions are required. The first converts text to base64

function convert-texttobase64{
        param([Parameter(Position = 0, Mandatory = $true, ValuefromPipeline = $true)][string]$rawtext)
        $1  = [System.Text.Encoding]::UTF8.GetBytes($rawtext)
        [System.Convert]::ToBase64String($1)
    }

The second is the actual meat of the script

function New-CMPowershellScript{
    param(
        [Parameter(Mandatory = $true)][string]$ScriptName,
        [Parameter()][string]$comment,
        [Parameter(Mandatory = $true)][string]$Script
    )
    $systemvar = @("Verbose","Debug","WarningAction","ErrorAction","InformationAction","ErrorVariable","WarningVariable","InformationVariable","OutVariable","OutBuffer","PipelineVariable")

    $tempscriptpath = "$($env:TEMP)\temp.ps1"
    $script | out-file $tempscriptpath
    $ParameterList = ((Get-Command -name $tempscriptpath).Parameters).Values | ?{$_.Name -notin $systemvar}
    Remove-Item $tempscriptpath -Force

    if($ParameterList.count -gt 0){
        [xml]$Doc = New-Object System.Xml.XmlDocument
 
        #create declaration
        $dec = $Doc.CreateXmlDeclaration("1.0","utf-16",$null)
        #append to document
        $doc.AppendChild($dec) | Out-Null

        $root = $doc.CreateNode("element","ScriptParameters",$null)
        $root.SetAttribute("SchemaVersion",1) | Out-Null

        ForEach($Parameter in $ParameterList){
            [string]$IsRequired=$Parameter.Attributes.Mandatory
            [string]$IsHidden=$Parameter.Attributes.DontShow
            [string]$description=$Parameter.Attributes.HelpMessage
            
            $P = $doc.CreateNode("element","ScriptParameter",$null)
            $P.SetAttribute("Name",$Parameter.Name) | Out-Null
            $P.SetAttribute("FriendlyName",$Parameter.Name) | Out-Null
            $P.SetAttribute("Type",$Parameter.ParameterType.FullName) | Out-Null
            $P.SetAttribute("Description",$description) | Out-Null
            $P.SetAttribute("IsRequired",$IsRequired.ToLower()) | Out-Null
            $P.SetAttribute("IsHidden",$IsHidden.ToLower()) | Out-Null

            if($Parameter.Attributes.ValidValues){
                $Values = $doc.CreateElement("Values")
                ForEach($value in $Parameter.Attributes.ValidValues){
                    $V = $doc.CreateElement("Value")
                    $V.InnerText = $value | Out-Null
                    $Values.AppendChild($v)
                }
                $p.AppendChild($values)

            }

            $root.AppendChild($P) | Out-Null
        }

        $doc.AppendChild($root) | Out-Null

        $tempfile = "$($env:TEMP)\paramtemp.xml"
        $doc.save($tempfile)
        [String]$params = Get-Content -Path $tempfile -Raw
        Remove-Item $tempfile -Force

    }

    if($null -eq (Get-Module ConfigurationManager)) {Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"}
    $psdrive = Get-PSDrive -PSProvider CMSite

    if($psdrive){
        $sitecode = $psdrive.SiteCode
    
        [string]$Script64 = convert-texttobase64 $Script
        if($Params){[string]$Params64 = convert-texttobase64 $Params}
    
        $NewGUID = ([guid]::NewGuid()).GUID
        $Arguments = @{
            ScriptGUID = $NewGUID;
            ScriptVersion = [string]'1';
            ScriptName = $ScriptName;
            Author = "$($env:userdomain)\$($env:username)";
            ScriptType = [uINT32]0;
            ApprovalState = [uINT32]0;
            Approver = $null;
            Comment = $null;
            ParamsDefinition = $Params64;
            ParameterlistXML = $null;
            Script = $Script64
        };
    
    
        Invoke-CimMethod -Namespace "root\SMS\site_$($sitecode)" -ClassName SMS_Scripts -MethodName CreateScripts -Arguments $Arguments
    }
    else{write-error "No CM provider loaded"}
}

Now put the script you want in ConfigMgr in a script block and call the function

$script = {
Param ([Parameter(Mandatory=$true)][string]$baselineName)

$Baseline = Get-WmiObject -Namespace root\ccm\dcm -Class SMS_DesiredConfiguration -filter "DisplayName='$baselineName'"

([wmiclass]'root\ccm\dcm:SMS_DesiredConfiguration').TriggerEvaluation($Baseline.Name, $Baseline.Version, $true, $true)
}

$BLScript = New-CMPowershellScript -ScriptName "PFE - Evaluate Specific Baseline" -Script $script