display top 6 recent blogpost

Posts

Add task sequence dependencies to DP group

, , , , , ,

We’re working on adding new distribution points that are used exclusively for imaging.  This motivated me to create a script to make sure all dependent packages are assigned to the proper distribution point group.  (You are using distribution point groups right?)

Change the site code in the script and it will prompt you for the task sequence and then prompt for the distribution point group.  After that, magic!  🙂

I wasn’t planning on posting this one yet as it could use a lot of polish but I’m sure it’ll evolve as time goes on and I’ll do my best to keep this updated.

On to the code…

 

$siteCode = "CM1"

Import-Module ConfigurationManager
Push-Location
Set-Location "$($siteCode):"

$site = (Get-CMSite -SiteCode $siteCode)
$sequences = Get-CMTaskSequence | Select-Object -Property Name | Out-GridView -Title "Select task sequences for DG" -PassThru
$dg = Get-CMDistributionPointGroup | Out-GridView -Title "Select a distribution point group." -PassThru

if($ConfirmPreference -eq 'Low') {$conf = @{Confirm = $true}}

foreach ($tsname in $sequences)
{
    $ts = Get-CMTaskSequence -Name $tsname.Name
    Write-Host "References $($ts.References.Count)"
    foreach ($ref in $ts.References)
    {
        $pkgContentServer = $null
        $pkgId = $null

        if ($ref.Type -eq 0)
        {
            $pkgContentServer = Get-WmiObject -ComputerName $site.ServerName -Namespace "rootSMSsite_$($site.SiteCode)" -Class SMS_PackageContentServerInfo -Filter "PackageID = '$($ref.Package)' AND ContentServerID = '$($dg.GroupID)'"
            $pkgId = $ref.Package
        }
        elseif ($ref.Type -eq 1)
        {
            $app = Get-CMApplication -ModelName $ref.Package
            $pkgContentServer = Get-WmiObject -ComputerName $site.ServerName -Namespace "rootSMSsite_$($site.SiteCode)" -Class SMS_PackageContentServerInfo -Filter "PackageID = '$($app.PackageID)' AND ContentServerID = '$($dg.GroupID)'"
            $pkgId = $app.PackageID
        }

        if ($pkgContentServer -eq $null)
        {
            Write-Host "Adding distribution point group $($dg.Name) to package $($ref.Package)."
            if ($PSCmdlet.ShouldProcess("$($ref.Package)", "Distribute package"))
            {
                if ($ref.Type -eq 0)
                {
                    $baseObject = Get-WmiObject -ComputerName $site.ServerName -Namespace "rootSMSsite_$($site.SiteCode)" -Class SMS_PackageBaseClass -Filter "PackageID = '$pkgId'"
                    Start-CMContentDistribution -InputObject ($baseObject | ConvertTo-CMIResultObject) -DistributionPointGroupName $dg.Name #-WhatIf:([bool]$WhatIfPreference.IsPresent) #@conf
                }
                elseif ($ref.Type -eq 1)
                {
                    Start-CMContentDistribution -ApplicationId $app.CI_ID -DistributionPointGroupName $dg.Name
                }
            }
        }
    }

    $bootImage = Get-CMBootImage -Id $ts.BootImageID
    $pkgContentServer = Get-WmiObject -ComputerName $site.ServerName -Namespace "rootSMSsite_$($site.SiteCode)" -Class SMS_PackageContentServerInfo -Filter "PackageID = '$($bootImage.PackageID)' AND ContentServerID = '$($dg.GroupID)'"
    if ($pkgContentServer -eq $null)
    {
        Write-Host "Adding distribution point group $($dg.Name) to package $($bootImage.PackageID)."
        if ($PSCmdlet.ShouldProcess("$($bootImage.PackageID)", "Distribute package"))
        {
            $baseObject = Get-WmiObject -ComputerName $site.ServerName -Namespace "rootSMSsite_$($site.SiteCode)" -Class SMS_PackageBaseClass -Filter "PackageID = '$($bootImage.PackageID)'"
            Start-CMContentDistribution -InputObject ($baseObject | ConvertTo-CMIResultObject) -DistributionPointGroupName $dg.Name
        }
    }
}
Pop-Location

Post OSD Scheduled Task

, ,

Post OSD Scheduled Task


Every organization handles OSD differently. Currently at our organization we do have some apps that have been ‘baked into the task sequence’  as an ‘Install Application’ step for a very long time and are needed on every single imaged machine. These work perfectly, install consistently and generally there are no ‘exceptions’ to a PC having the software.

This isn’t the case with all of our widely distributed applications, and rather than build the logic into the task sequence we let the collections do the work after imaging. After a period of time when the appropriate collections are updated on their various schedules these new machines happily receive their software and baselines and go on their merry way as we know all healthy SCCM clients do! In the day-to-day SCCM world this works perfectly fine. Machines are added to collections through whatever method your organization uses, whether it be Direct Membership, AD Group/OU Queries, name based queries, hardware inventory based queries and there is a general understanding that machines will pop into the collection and receive their Applications/Updates/CIs in good time.

When our techs image a machine it can be helpful for it to temporarily have some expedited policy refresh rates for a period of time to speed up those after-the-fact deployments. We had tried a few collection queries to catch these ‘new machines’ so that we could deploy some aggressive Client Settings to them, but generally there is never a perfect query. Usually you catch not just new machines but instead those risen from the dead pit of being stale, or they were rejoined to the domain, maybe the client was reinstalled or some form of in place upgrade happened.  As an alternative to the collection query I wrote up a Powershell script.


What Does It Do?


It creates a scheduled task! 

The script can be ran from a ‘Run Command Line’ or ‘Run Powershell Script’ step during OSD (Typically near/at the end) with various parameter options. You’ll have to toss it into a package to serve up to the task sequence of course. It will create a scheduled task that runs specified SCCM Client Policy requests at whatever interval you want for as long as you want. Also, the task deletes itself shortly after the duration has passed. I didn’t quite include all the of schedule types because there is a very long list. But most of the key ones are there and any others can be easily added. 


How Do I Use It?


New-ClientActionScheduledTask.ps1 -Schedule MachinePol -Interval 5

This will create a schedule task that runs every 5 minutes for 24 hours. I did say aggressive at least once up there right? The task will receive a generated name based on the requested schedules. The above would produce a task named –
SCCM Action Scheduler – [MachinePol]
Which calls a file (Start-CMClientAction.ps1)  that is generated and stored in c:\windows\temp

Both the task name and the file name can be specified as parameters to the script aptly named… -FileName and -TaskName


New-ClientActionScheduledTask.ps1 -Schedule AppEval, HardwareInv, UpdateEval, UpdateScan -Interval 30 -Duration 12
Task Sequence Example

Similar results with this execution of the line above. A task is created, but with it running every 30 minutes for a 12 hour period. The task will appear in task scheduler with a title –
SCCM Action Scheduler – [AppEval,HardwareInv,UpdateEval,UpdateScan]
Which is based on the actions provided.


Where Do I Git It?


GitHub! I intend to continue adding to this GitHub repository.

https://github.com/CodyMathis123/CM-Ramblings/blob/master/New-ClientActionScheduledTask.ps1


Neat Stuff:


I’ve also used a couple of bits of code you might find interesting to create the scheduled task. Maybe you’ve seen it, maybe you haven’t.


${Function:Start-CMClientAction}

This will write-out the contents of many functions (note: function, not cmdlet). I am leveraging this to generate a .ps1 file that can be easily invoked by the task sequence.

Neat right? I wrote that function though… but what is more interesting is you can do this with some built in functions! Try ${function:Clear-Host} or ${function:Get-Verb} and you can see some of the magic behind at least some commands you’ve used. Many are compiled cmdlets and are simply not expandable like this but can be dug into in other ways.


$TaskDefinition.Settings.DeleteExpiredTaskAfter = "PT0S"

https://docs.microsoft.com/en-us/windows/desktop/taskschd/tasksettings-deleteexpiredtaskafter

While this can be a bit odd to work with, the above piece of code allows our scheduled task to ‘delete itself’ after it expires. Specifically… zero seconds after it expires. T is just a delimiter between date and time (Eg. days vs hours/minutes/seconds). You will need to specify an ‘EndBoundary’ for this to function, which is what our ‘Duration’ is in this.


function New-ScheduledTaskTimeString {
    param(
        [Parameter(Mandatory = $false)]
        [int]$Hours = 0,
        [Parameter(Mandatory = $false)]
        [int]$Minutes = 0
    )
    $TimeSpan = New-TimeSpan -Hours $Hours -Minutes $Minutes
    $TimeSpanDays = $TimeSpan | Select-Object -ExpandProperty Days
    $TimeSpanHours = $TimeSpan | Select-Object -ExpandProperty Hours
    $TimeSpanMinutes = $TimeSpan | Select-Object -ExpandProperty Minutes

    if ($TimeSpanDays -gt 0) {
        $OutputDays = [string]::Format("{0}D", $TimeSpanDays)
    }

    if ($TimeSpanHours -gt 0 -or $TimeSpanMinutes -gt 0) {
        $Delimiter = 'T'
        if ($TimeSpanHours -gt 0) {
            $OutputHours = [string]::Format("{0}H", $TimeSpanHours)
        }

        if ($TimeSpanMinutes -gt 0) {
            $OutputMinutes = [string]::Format("{0}M", $TimeSpanMinutes)
        }
    }

    [string]::Format("P{0}{1}{2}{3}", $OutputDays, $Delimiter, $OutputHours, $OutputMinutes)

}

You give me hours and minutes, I give you a PnDTnHnMnS string to use for a scheduled task.
Just a quick function I wrote for the purposes of dumping out usable strings for time intervals that task scheduler understands.


I opted to not use the *-ScheduledTask* commands available post-Win 7. You could ‘simplify’ the code a bit for the task creation if you don’t mind being incompatible with Windows 7 by using these.

Is this the right way to do it? Who knows! I’m sure with some very careful inspection and categorization of our collections and their refresh schedules we could help the situation in other ways. Still a neat bit of code.

Cody Mathis
@CodyMathis123

N-2 WIM and Task Sequence Managment

, ,

After my experience at MMSDE and talking with not only other attendees, but speakers that I knew of and those that were new to me, I felt like I could add something that could help others out there as I have been helped along the way.

Managing multiple WIM files and task sequences can be genuinely time consuming, especially if you have been doing it manually.  That was why I’ve been automating as much as possible over the last year.  Extracting WIM files, making them patch current every month, and then building and capturing WIM files with various versions of Office.  Most of which I have been able to script through trial and error as I got a chance to.

What I wanted to share is my script for updating our Operating Systems and Upgrade packages using the N-2 method.  It is my latest script, but it can help set up your WIM files in the same way and see when I will post the other processes in the near future.   I do not currently have a lab environment to get screenshots yet, but that will also change as I figure out which way to go.

For this script, you can run this on any machine that has the Admin Console installed on it.

These are all examples of how the script is currently set up, to give you an idea for naming conventions for static packages that you can use and customize the script for your own environment.

NEW_1809_WIM

_PROD_1809_WIM

1_PREV_1809_WIM

2_PREV_1809_WIM

For the upgrade packages, it is similar, however they are based on language using EN-US as the example.

NEW_1809_en-us

_PROD_1809_en-us

1_PREV_1809_en-us

2_PREV_1809_en-us

NEW would be your newly updated WIM files and the _PROD your current WIM files.  1_PREV the previous month, and 2_PREV being two versions older.

Here are the steps to acquire your OSD WIM package ID’s:

#=+=+=+=+=+=+=+= OSD WIMS =+=+=+=+=+=+=+=
$NEWWIM = (Get-CMOperatingSystemImage -Name “NEW_*$OSVersion*WIM”)
$NEWWIMID = ($NEWWIM.package.ID)

$PRODWIM = (Get-CMOperatingSystemImage -Name “_PROD_*$OSVersion*WIM”)
$PRODWIMID = ($PRODWIM.package.ID)

$PREV1WIM = (Get-CMOperatingSystemImage -Name “1_PREV_*$OSVersion*WIM”)
$PREV1WIMID = ($PREV1WIM.package.ID)

$PREV2WIM = (Get-CMOperatingSystemImage -Name “2_PREV_*$OSVersion*WIM”)
$PREV2WIMID = ($PREV2WIM.package.ID)

Similarly, the following commands will find your upgrade packages:

#=+=+=+=+=+=+=+= Upgrade Packages =+=+=+=+=+=+=+=
# Using the US-EN version as an example
$NEW_ENUS = (Get-CMOperatingSystemInstaller -Name “NEW_*$OSVersion*en-us”)
$NEW_ENUSID = ($NEW_ENUS.package.ID)

$PROD_ENUS = (Get-CMOperatingSystemInstaller -Name “_PROD_*$OSVersion*en-us”)
$PROD_ENUSID = ($PROD_ENUS.package.ID)

$PREV1_ENUS = (Get-CMOperatingSystemInstaller -Name “1_PREV_*$OSVersion*en-us”)
$PREV1_ENUSID = ($PREV1_ENUS.package.ID)

$PREV2_ENUS = (Get-CMOperatingSystemInstaller -Name “2_PREV_*$OSVersion*en-us”)
$PREV2_ENUSID = ($PREV2_ENUS.package.ID)

Using the values obtained above, we first change the names of the OSD and Upgrade packages.

#=+= OSD =+=
Set-CMOperatingSystemImage -Id $PREV2WIMID -NewName “RETIRED_$OSVersion _WIM” -Description “RETIRED”
Set-CMOPeratingSystemImage -Id $PREV1WIMID -NewName “2_PREV_$OSVersion _WIM” -Description “2-Previous Month”
Set-CMOperatingSystemImage -Id $PRODWIMID -NewName “1_PREV_$OSVersion _WIM” -Description “1-Previous Month”
Set-CMOperatingSystemImage -Id $NEWWIMID -NewName “_PROD_$OSVersion _WIM” -Description “Current Month”

#=+= Upgrade =+=
Set-CMOperatingSystemInstaller -Id $PREV2_ENUSID -NewName “RETIRED_$OSVersion _en-us” -Description “RETIRED”
Set-CMOPeratingSystemInstaller -Id $PREV1_ENUSID -NewName “2_PREV_$OSVersion _en-us” -Description “2-Previous Month”
Set-CMOperatingSystemInstaller -Id $PROD_ENUSID -NewName “1_PREV_$OSVersion _en-us” -Description “1-Previous Month”
Set-CMOperatingSystemInstaller -Id $NEW_ENUSID -NewName “_PROD_$OSVersion _en-us” -Description “Current Month”

This will cycle the WIM packages to make the NEW the _PROD and the others trickle down to where 2_PREV becomes RETIRED.

(Note, one of my other processes that I will post will automatically use the RETIRED package and rename them NEW when building the new WIM files.)

Now, this next step you will have to customize the script for your environment.  This will update your task sequences with the _PROD packages.

#=+=+=+=+=+=+=+= Update Task Sequences =+=+=+=+=+=+=+=
# If managing multiple versions, you can set an if ($OSVersion = XXX) also and have the following steps between {}.
#=+= OSD =+=
$PRODWIM = (Get-CMOperatingSystemImage -Name “_PROD_*$OSVersion*WIM”)

Set-CMTSStepApplyOperatingSystem -TaskSequenceId XXXXXXXX -ImagePackage $PRODWIM -ImagePackageIndex 1 #Change XXXXXXXX to your task sequence ID.

#=+= Upgrade =+=
$PROD_ENUS = (Get-CMOperatingSystemInstaller -Name “_PROD_*$OSVersion*en-us”)

Set-CMTSStepUpgradeOperatingSystem -TaskSequenceId XXXXXXXX -UpgradePackage $PROD_ENUS #Change XXXXXXXX to your task sequence ID.

And that’s it.

I have uploaded the script to GitHub so you can grab it instead of copying and pasting from above.

https://github.com/shotgn22/osd

Thank you for reading this far.

My name is Scott Graves, I have been doing IT since 1995 and am currently heavy into OSD, Task Sequences and Upgrades.  I have set up a Twitter (@shotgn22) if you have any questions.  Thank you Adam Gross of ASquareDozen.com and Chris Buck of this page for taking the time to answer my questions the past few days.