SCCM 2012 R2 – Automatically populate and balance device collections with PowerShell 3.0

I had to recently throw a script together to address a need to automatically populate collections so that software could be deployed to a large environment in stages.

The designed solution was very interesting. Rather than deploying to 80,000 devices in a single hit, it was decided to deploy to deploy to 50 pilot users on Monday, 3,000 on Tuesday, 3,000 on Wednesday, 3,000 on Thursday and then all remaining machines on Friday.

To accomplish this, maintenance windows were introduced. This left one problem, how do you make sure the groups stay populated without someone having to manually count out 3,000 machine per group? It also got pointed out that a large hardware migration was imminent and trying to keep track of which ones were and were not in the group was going to get very complicated. Many ideas were thrown around including using exclude queries, but they all had the same downside… They required someone to regularly maintain these collections.

I turned to my favourite place for the solution, PowerShell.

This script is a very manual script and you will need to make sure that you have device collections to match the ones in the script. The only one that can be easily changed is the source collection. When I have time to sit down and refine this script I will post it here so that you can transfer it to your site easier

I am not going to go into details on each section of the script at this point but I will post it here if anyone wants something like this

###############################################################################
####                                                                       ####
####                                                                       ####
####                Maintenance Windows Collection Autofill                ####
####                            Version 1.0.1                              ####
####                       Created by: Liam Matthews                       ####
####                                                                       ####
####                                                                       ####
###############################################################################
 
################################Release notes##################################
####                                                                       ####
################################### 1.0.0 #####################################
#### - Initial release                                                     ####
####                                                                       ####
################################### 1.0.1 #####################################
#### - Changed source collection to be a variable                          ####
####                                                                       ####
###############################################################################
 
##User defined variables
$SCCMSiteCode = "A00"
$LogLocation = "E:\Scripts\"
$Component = "Maintenance Windows Collection Autofill"
$LogFileName = "Maintenance Windows Collection Autofill.log"
$SourceCollection = "Maintenance Windows - Scope"
 
##Machine Variables
$SCCMModule = $env:SMS_ADMIN_UI_PATH.Replace("\bin\i386","\bin\configurationmanager.psd1")
$CurrentUser = [Environment]::UserName
$SCCMDrive = $SCCMSiteCode + ":"
$StartTime = Get-Date -Format "HH:mm:ss.fff"
$StartDate = Get-Date -Format "MM-dd-yyyy"
$StartDateTime = Get-Date -Format "HHmmss"
$TZbias = (Get-WmiObject -Query "Select Bias from Win32_TimeZone").bias
 
##Remove log file if larger than 1MB
Get-Item 'E:\Scripts\Maintenance Windows Collection Autofill.log' | Where {$_.Length -gt 1048576} | Remove-Item
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "+++Starting new thread+++"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Checking for Configuration 1Manager PowerShell module"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Get list of  current PowerShell modules
$CurrentModules = Get-Module
 
IF ($CurrentModules.Name -contains "ConfigurationManager")
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "2"
    $LogText = "Configuration Manager PowerShell module has already been imported. This is unexpected but script will continue"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
}
ELSE
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "Configuration Manager PowerShell module is not currently present. Attempting import now"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Checking if SCCM PowerShell module is available for import
    IF (Test-Path $SCCMModule)
    {
        ##Import SCCM PowerShell module
        Import-Module $SCCMModule
 
        ##Get list of  current PowerShell modules
        $CurrentModules = Get-Module
             
        IF ($CurrentModules.Name -contains "ConfigurationManager")
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = "Configuration Manager PowerShell module has been imported successfully"
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
        }
        ELSE
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "3"
            $LogText = "Configuration Manager PowerShell module has failed to import. The script will now Exit"
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
             
            Exit
        }
    }
    ELSE
    {
        ##Output to Log
        $Time = Get-Date -Format "HH:mm:ss.fff"
        $Date = Get-Date -Format "MM-dd-yyyy"
        $Type = "3"
        $LogText = "Configuration Manager PowerShell module does not exist on the current machine. The script will now exit"
        $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
        Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
         
        Exit
    }
}
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Checking for SCCM Drive: $SCCMSiteCode"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Get list of PowerShell drives
$PSDrives = Get-PSDrive | Select Name
 
IF ($PSDrives.Name -contains $SCCMSiteCode)
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "$SCCMSiteCode drive exists"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "Connecting to drive $SCCMSiteCode"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Connect to SCCM site
    CD $SCCMDrive
 
    ##Get current drive location
    $CurrentPSDrive = (get-location).Drive.Name
 
    IF ($CurrentPSDrive -eq "$SCCMSiteCode")
    {
        ##Output to Log
        $Time = Get-Date -Format "HH:mm:ss.fff"
        $Date = Get-Date -Format "MM-dd-yyyy"
        $Type = "1"
        $LogText = "Successfully connected to $SCCMSiteCode"
        $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
        Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
    }    
    ELSE
    {
        ##Output to Log
        $Time = Get-Date -Format "HH:mm:ss.fff"
        $Date = Get-Date -Format "MM-dd-yyyy"
        $Type = "3"
        $LogText = "Unable to connect to $SCCMSiteCode. The script will now exit"
        $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
        Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
        Exit
    }
}
ELSE
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "3"
    $LogText = "SCCM drive $SCCMSiteCode does not exist. Module may have issues or variable may be incorrect. The script will now exit"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    Exit
}
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Searching for SCCM CAS site"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Get SCCM CAS Server
$CASServerName = Get-CMSite | Where {$_.Type -eq "4"}
$CasServerName = $CASServerName.ServerName
 
IF ($CASServerName -ne $Null)
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "Found SCCM CAS site $CASServerName"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
}
ELSE
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "3"
    $LogText = "Unable to locate SCCM CAS site. The script will now exit"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    Exit
}
 
#Create blank array
$Devicestoadd = @()
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Identifying source collection"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Get scope device collection
$MaintenanceWindowCollection = Get-CMDeviceCollection -Name $SourceCollection
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Getting list of devices in source collection"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##List devices in scope collection
$CollectionMembers = Get-WmiObject -ComputerName $CASServerName -Class SMS_CM_RES_COLL_$($MaintenanceWindowCollection.CollectionID) -Namespace root\sms\site_$SCCMSiteCode
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Identifying device membership"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
foreach ($CollectionMember in $CollectionMembers)
{
    ##List all collections that devices are members of
    $AllCollections = @()
    $ResID = (Get-CMDevice -Name $($CollectionMember.Name)).ResourceID
    $Collections = (Get-WmiObject -ComputerName $CASServerName -Class sms_fullcollectionmembership -Namespace root\sms\site_$SCCMSiteCode -Filter "ResourceID = '$($ResID)'").CollectionID
    foreach ($Collection in $Collections)
    {
        $DeviceCollectionMembership = Get-CMDeviceCollection -CollectionId $Collection | select Name, CollectionID
        $AllCollections += $DeviceCollectionMembership
    }
 
    IF ($AllCollections.name -Like "Maintenance Windows - Phase*")
    {
        ##Output to Log
        $Time = Get-Date -Format "HH:mm:ss.fff"
        $Date = Get-Date -Format "MM-dd-yyyy"
        $Type = "1"
        $LogText = $CollectionMember.Name + " is already part of a maintenance window collection"
        $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
        Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
    }
    ELSE
    {
        IF ($Devicestoadd.name -contains $CollectionMember.Name)
        {
        }
        ELSE
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = $CollectionMember.Name + " is not part of a maintenance window collection. Adding it to list to manage"
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
             
            ##Add device to array
            $Devicestoadd += $CollectionMember
        }
    }
 
}
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "Getting list destination collections"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Identify destination collections
$Phase2Collection = Get-CMDeviceCollection -Name "Maintenance Windows - Phase 2"
$Phase3Collection = Get-CMDeviceCollection -Name "Maintenance Windows - Phase 3"
$Phase4Collection = Get-CMDeviceCollection -Name "Maintenance Windows - Phase 4"
$Phase5Collection = Get-CMDeviceCollection -Name "Maintenance Windows - Phase 5"
 
$Phase2CollectionCount = $Phase2Collection.LocalMemberCount
$Phase3CollectionCount = $Phase3Collection.LocalMemberCount
$Phase4CollectionCount = $Phase4Collection.LocalMemberCount
$Phase5CollectionCount = $Phase5Collection.LocalMemberCount
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = $Phase2Collection.Name + " currently has " + $Phase2CollectionCount + " Members"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = $Phase3Collection.Name + " currently has " + $Phase3CollectionCount + " Members"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = $Phase4Collection.Name + " currently has " + $Phase4CollectionCount + " Members"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = $Phase5Collection.Name + " currently has " + $Phase5CollectionCount + " Members"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
IF ($Devicestoadd.Count -eq "0")
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "No new devices to add to collections"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
}
ELSE
{
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = "Adding devices to maintenance window collections"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    foreach ($Devicetoadd in $Devicestoadd)
    {
        #Find collections to add device to
        IF (($Phase2CollectionCount -lt "3000") -and ($Phase2CollectionCount -le $Phase3CollectionCount) -and ($Phase2CollectionCount -le $Phase4CollectionCount))
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = "Adding device: " + $CollectionMember.Name + " to device collection: " + $Phase2Collection.Name
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
            #Add device to collections
            Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Phase2Collection.Name -ResourceID $Devicetoadd.ResourceID
 
            #Manually increase collection count
            $Phase2CollectionCount += 1
 
        }
        ELSEIF (($Phase3CollectionCount -lt "3000") -and ($Phase3CollectionCount -le $Phase2CollectionCount) -and ($Phase3CollectionCount -le $Phase4CollectionCount))
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = "Adding device: " + $CollectionMember.Name + " to device collection: " + $Phase3Collection.Name
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
            #Add device to collections
            Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Phase3Collection.Name -ResourceID $Devicetoadd.ResourceID
 
            #Manually increase collection count
            $Phase3CollectionCount += 1
        }
        ELSEIF (($Phase4CollectionCount -lt "3000") -and ($Phase4CollectionCount -le $Phase2CollectionCount) -and ($Phase4CollectionCount -le $Phase3CollectionCount))
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = "Adding device: " + $CollectionMember.Name + " to device collection: " + $Phase4Collection.Name
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
            #Add device to collections
            Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Phase4Collection.Name -ResourceID $Devicetoadd.ResourceID
 
            ##Manually increase collection count
            $Phase4CollectionCount += 1
        }
        ELSEIF (($Phase4CollectionCount -ge "3000") -and ($Phase4CollectionCount -ge "3000") -and ($Phase4CollectionCount -ge "3000"))
        {
            ##Output to Log
            $Time = Get-Date -Format "HH:mm:ss.fff"
            $Date = Get-Date -Format "MM-dd-yyyy"
            $Type = "1"
            $LogText = "Adding device: " + $CollectionMember.Name + " to device collection: " + $Phase5Collection.Name
            $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
            Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
            #Add device to collections
            Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Phase5Collection.Name -ResourceID $Devicetoadd.ResourceID
 
            ##Manually increase collection count
            $Phase5CollectionCount += 1
        }
    }
 
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = $Phase2Collection.Name + " now has " + $Phase2CollectionCount + " Members"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = $Phase3Collection.Name + " now has " + $Phase3CollectionCount + " Members"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = $Phase4Collection.Name + " now has " + $Phase4CollectionCount + " Members"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
 
    ##Output to Log
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $Type = "1"
    $LogText = $Phase5Collection.Name + " now has " + $Phase5CollectionCount + " Members "
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"
}
 
##Output to Log
$Time = Get-Date -Format "HH:mm:ss.fff"
$Date = Get-Date -Format "MM-dd-yyyy"
$Type = "1"
$LogText = "+++Thread finished+++"
$LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($CurrentUser)`">"
Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$LogLocation$LogFileName"

SCCM 2012 R2 – Exporting an application with PowerShell

Recently i have been doing some work for a client that likes to segregate their environment for test and production. While this is great for many reasons, one of the downsides is having to export an application and then import it into the next environment.

In this post i will be showing a PowerShell script that I developed to export applications from SCCM 2012 R2 to a predetermined folder. I will also do my best to explain each step of the script. As usual this script should work on SCCM 2012 and SCCM 2012 SP1 but I have only tested it on SCCM 2012 R2


##Script written by Liam Matthews 2015

##Variable list
$SCCMModule = “C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1”
$SCCMSiteCode = “001”
$SCCMDrive = $SCCMSiteCode + “:”
$SCCMServer = “SCCM2012.liammatthewsit.com”
$ExportPathDirect = “E:\SCCM Export\”

##Progress bar
Write-Progress -Activity “Application Export” -Status “Starting Script” -PercentComplete 10

##Import SCCM PowerShell module
Import-Module $SCCMModule

##Connect to SCCM site
CD $SCCMDrive

##Pause to show previous display message
Start-Sleep 5

##Progress bar
Write-Progress -Activity “Application Export” -Status “Searching for applications” -PercentComplete 30

##Get applicaion list via WMI
$Applications = Get-WMIObject -ComputerName $SCCMServer -Namespace Root\SMS\Site_$SCCMSiteCode -Class “SMS_Application” | Select -unique LocalizedDisplayName | sort LocalizedDisplayName

##Application Import Selection Form
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form1 = New-Object System.Windows.Forms.Form
$form1.Text = “Application Import”
$form1.Size = New-Object System.Drawing.Size(425,380)
$form1.StartPosition = “CenterScreen”

$OKButton1 = New-Object System.Windows.Forms.Button
$OKButton1.Location = New-Object System.Drawing.Point(300,325)
$OKButton1.Size = New-Object System.Drawing.Size(75,23)
$OKButton1.Text = “OK”
$OKButton1.DialogResult = [System.Windows.Forms.DialogResult]::OK
$OKButton1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
$form1.AcceptButton = $OKButton1
$form1.Controls.Add($OKButton1)

$CancelButton1 = New-Object System.Windows.Forms.Button
$CancelButton1.Location = New-Object System.Drawing.Point(225,325)
$CancelButton1.Size = New-Object System.Drawing.Size(75,23)
$CancelButton1.Text = “Cancel”
$CancelButton1.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$CancelButton1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
$form1.CancelButton = $CancelButton1
$form1.Controls.Add($CancelButton1)

$label1 = New-Object System.Windows.Forms.Label
$label1.Location = New-Object System.Drawing.Point(10,5)
$label1.Size = New-Object System.Drawing.Size(280,20)
$label1.Text = “Select an application to import”
$form1.Controls.Add($label1)

$listBox1 = New-Object System.Windows.Forms.Listbox
$listBox1.Location = New-Object System.Drawing.Size(10,30)
$listBox1.Width = 400
$listBox1.Height = 296
$listBox1.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right

##Add items to form
foreach($Application in $Applications)
{
[void] $ListBox1.Items.Add($Application.LocalizedDisplayName)
}

$form1.Controls.Add($listBox1)
$form1.Topmost = $True
$result1 = $form1.ShowDialog()
if ($result1 -eq [System.Windows.Forms.DialogResult]::OK)
{
$SelectedApplication = $listBox1.SelectedItems
$SelectedApplication = $SelectedApplication[0]
}
else
{
exit
}

##Progress bar
Write-Progress -Activity “Application Export” -Status “Exporting $SelectedApplication” -PercentComplete 60

##Export selected application
$ExportPath = $ExportPathDirect + $SelectedApplication + “.zip”
Export-CMApplication -IgnoreRelated -Path $ExportPath -Name $SelectedApplication

##Progress bar
Write-Progress -Activity “Application Export” -Status “Finalizing” -PercentComplete 90

##Rename exported folders to deployment type name
$DeploymentTypes = Get-CMDeploymentType -ApplicationName $SelectedApplication
foreach ($DeploymentType in $DeploymentTypes)
{
$OldContentName = $ExportPathDirect + $SelectedApplication + “_files\” + $DeploymentType.ContentId
$NewContentName = $DeploymentType.LocalizedDisplayName
$NewContentName = $NewContentName.Replace(“|”,””)
$NewContentName = $NewContentName.Replace(“\”,””)
$NewContentName = $NewContentName.Replace(“/”,””)
$NewContentName = $NewContentName.Replace(“?”,””)
$NewContentName = $NewContentName.Replace(“*”,””)
$NewContentName = $NewContentName.Replace(“<“,””)
$NewContentName = $NewContentName.Replace(“>”,””)
Rename-Item -NewName $NewContentName -Path $OldContentName
}

##Progress bar
Write-Progress -Activity “Application Export” -Status “Export of $SelectedApplication Completed” -PercentComplete 100

##Pause to show previous display message
Start-Sleep 5


OK, so there is the script in its entirety, lets go ahead and start breaking this down

I will be skipping over anything to do with the progress bar and anything that has Start-Sleep as these are both for the user to see


##Variable list

$SCCMModule = “C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1”
$SCCMSiteCode = “001”
$SCCMDrive = $SCCMSiteCode + “:”
$SCCMServer = “SCCM2012.liammatthewsit.com”
$ExportPathDirect = “E:\SCCM Export\”

In this section we are defining the variables that we will be using in the script. Not all variables can be created at this stage because they may rely on information that we do not currently have, anything that does not will be listed here so that it is easy to find


##Import SCCM PowerShell module
Import-Module $SCCMModule

##Connect to SCCM site
CD $SCCMDrive

These commands import the SCCM module and then connect to the appropriate drive (Which would be your site code)


##Get applicaion list via WMI
$Applications = Get-WMIObject -ComputerName $SCCMServer -Namespace Root\SMS\Site_$SCCMSiteCode -Class “SMS_Application” | Select -unique LocalizedDisplayName | sort LocalizedDisplayName

This will connect to WMI on the SCCM server and fetch a list of all applications that currently exist, note that it filters out duplicated via “Select -unique”. Within WMI there will be several versions of an application listed, this is because each revision will be displayed as though it is a different application

Some people might be asking “Why are you not using the module command to get a list of applications?” I have used WMI because it is a faster query than “Get-CMApplication”. This may not be a big issue if you are using this in a lab or you only have a few dozen applications, but when the number of applications exceeds 500 then you might as well take your lunch break while it runs

If you are keen you can change this line to the following (you will also be able to remove the $SCCMServer variable

$Applications = Get-CMApplication | Select LocalizedDisplayName | sort LocalizedDisplayName


##Application Import Selection Form
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form1 = New-Object System.Windows.Forms.Form
$form1.Text = “Application Import”
$form1.Size = New-Object System.Drawing.Size(425,380)
$form1.StartPosition = “CenterScreen”

$OKButton1 = New-Object System.Windows.Forms.Button
$OKButton1.Location = New-Object System.Drawing.Point(300,325)
$OKButton1.Size = New-Object System.Drawing.Size(75,23)
$OKButton1.Text = “OK”
$OKButton1.DialogResult = [System.Windows.Forms.DialogResult]::OK
$OKButton1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
$form1.AcceptButton = $OKButton1
$form1.Controls.Add($OKButton1)

$CancelButton1 = New-Object System.Windows.Forms.Button
$CancelButton1.Location = New-Object System.Drawing.Point(225,325)
$CancelButton1.Size = New-Object System.Drawing.Size(75,23)
$CancelButton1.Text = “Cancel”
$CancelButton1.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$CancelButton1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
$form1.CancelButton = $CancelButton1
$form1.Controls.Add($CancelButton1)

$label1 = New-Object System.Windows.Forms.Label
$label1.Location = New-Object System.Drawing.Point(10,5)
$label1.Size = New-Object System.Drawing.Size(280,20)
$label1.Text = “Select an application to import”
$form1.Controls.Add($label1)

$listBox1 = New-Object System.Windows.Forms.Listbox
$listBox1.Location = New-Object System.Drawing.Size(10,30)
$listBox1.Width = 400
$listBox1.Height = 296
$listBox1.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right

This is a long one, I am not going to go through each line one at a time. this section creates a list box. The bulk of this code was taken from the Microsoft Technet article on list boxes, however is was modified to make it expandable when dragged


##Add items to form
foreach($Application in $Applications)
{
[void] $ListBox1.Items.Add($Application.LocalizedDisplayName)
}

$form1.Controls.Add($listBox1)
$form1.Topmost = $True
$result1 = $form1.ShowDialog()
if ($result1 -eq [System.Windows.Forms.DialogResult]::OK)
{
$SelectedApplication = $listBox1.SelectedItems
$SelectedApplication = $SelectedApplication[0]
}
else
{
exit
}

Here we are feting the data from the $Applications variable that we specified earlier and turning each entry into an item in the list box. The bottom half of this code also specifies the action that is taken when the OK button is pressed. In this case it is to assign the selected item to a variable that we can use later


##Export selected application
$ExportPath = $ExportPathDirect + $SelectedApplication + “.zip”
Export-CMApplication -IgnoreRelated -Path $ExportPath -Name $SelectedApplication

This is where all the magic happens. Here we are using all the variables that we have created to export the application to the folder the we specified at the top of the script


##Rename exported folders to deployment type name
$DeploymentTypes = Get-CMDeploymentType -ApplicationName $SelectedApplication
foreach ($DeploymentType in $DeploymentTypes)
{
$OldContentName = $ExportPathDirect + $SelectedApplication + “_files\” + $DeploymentType.ContentId
$NewContentName = $DeploymentType.LocalizedDisplayName
$NewContentName = $NewContentName.Replace(“|”,””)
$NewContentName = $NewContentName.Replace(“\”,””)
$NewContentName = $NewContentName.Replace(“/”,””)
$NewContentName = $NewContentName.Replace(“?”,””)
$NewContentName = $NewContentName.Replace(“*”,””)
$NewContentName = $NewContentName.Replace(“<“,””)
$NewContentName = $NewContentName.Replace(“>”,””)
Rename-Item -NewName $NewContentName -Path $OldContentName
}

This section is entirely optional, however, when the application is exported is puts the source files in a folder which is named with the Deployment Type GUID. When we are using a script to import this we will run into several problems because it will have no idea which folder belongs to which Deployment Type

This part of the script will rename each folder to the Deployment Type name so that it is easier to reference this later. When using this script though you will need to make sure that every Deployment Type name in your environment is unique, otherwise you are going to run into duplicate folder problems


Hopefully this script will prove useful and you have a good understanding of how it functions so that you can modify to suite your environment. If you have any questions or don’t understand something in the script then please comment

Stay tuned to the import script that will be following shortly

SCCM 2012 R2 – Deploying Microsoft Office 2013 Professional Plus

In this post I will give detailed instructions on how to deploy Microsoft Office 2013 Professional Plus

While I have done this with SCCM 2012 R2 in this guide the process is identical for SCCM 2012, 2012 SP1 and Microsoft Office 2010 Professional Plus

  1. Navigate to the location of the install media and open a CMD windowSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 01
  2. Type “setup.exe /admin” and press EnterSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 02
  3. When the product selection screen appears, select the product you wish to install (there will usually only be one)SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 03
  4. Navigate to “Install location and organization name”
  5. Type in your organization name (This step is not required)SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 05
  6. Navigate to “Licensing and User Interface”
  7. At my location I have a KMS server running which services all my clients for me. If you do not have this you can enter you MAK key here
  8. Click “I accept the terms in the License Agreement”
  9. Change “Display level” to “None”
  10. Ensure “Complete notice” remains unchecked and check “Suppress modal” and “No cancel”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 06
  11. Navigate to “Modify user settings”
  12. In the left of the two panes, navigate to “Microsoft Office 2013\Privacy\Trust Center”
  13. In the right pane double click on “Disable Opt-in Wizard on first run”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 07
  14. Select “Enabled” and click “OK”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 08
  15. Confirm that the status is now set to enabledSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 09
  16. Navigate to “Set feature installation states”
  17. In the right pane, click on “Microsoft Office” at the top of the tree and select “Run all from My Computer” from the drop-down menuSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 10
  18. One this is done save the MSP file. I have chosen to create a folder in the source media folder and call it “AdminFiles” and then the file name as Full.MSP, so that i can create individual installs for Excel, Word, etc. you can set any name, but it is much easier later if you don’t include spaces in the nameSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 11
  19. Within the SCCM console, navigate to “Software Library”, “Application Management”.
  20. Right click on “Applications” or a folder that you have created under this and click “Create Application”.SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 12
  21. Select “Manually specify the application information” then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 13
  22. Fill the the appropriate fields with the information you wish to provide, then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 14
  23. Fill the the appropriate fields with the information you wish to provide. If you wish to have a custom icon for the Application Catalog choose it here, then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 15
  24. When you reach the “Deployment Types” field, click “Add”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 16
  25. Within the wizard that opens up, select “Manually specify the deployment type information”. Then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 17
  26. In the “Name” field, type “Microsoft Office 2013 Professional Plus 32-Bit” as this is a 32-Bit deploymentSCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 18
  27. Enter the content location of the source media (extracted folder obviously)
  28. In the “Installation program” field, type “Setup.exe /AdminFile AdminFiles\Full.MSP” (Or whatever you called your MSP file)
  29. In the “Uninstall program” field, type “Setup.exe /Uninstall PROPLUS”
  30. Tick “Run installation and uninstall program as 32-bit process on 64-bit clients” and click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 19
  31. Click “Add Clause”
  32. Change “Setting Type” to “Windows Installer”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 20
  33. In the “Product code” field, click “Browse”
  34. Navigate to the source media, then proplus.ww. Select “proplusww.msi” and click “Open”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 21
  35. Select “This MSI product code must exit on the target system and the following condition must be met to indicate the presence of  this application”. Then click “OK”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 22
  36. Review information and click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 23
  37. Change “Installation behavior” to “Install for system”
  38. Change “Logon requirements” to “Whether or not a user is logged on”
  39. Change “Installation program visibility” to “Hidden”
  40. Change “Estimated installation time (minutes)” to “20”, then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 24
  41. If you would like to set requirements for the installation, such as minimum system requirements or primary device you can do so here, otherwise click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 25
  42. If you would like to set a dependency for this installation such as .Net you can do so here, otherwise click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 26
  43. Review all details here then click “Next”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 27
  44. Click “Close”SCCM 2012 R2 - Deploying Microsoft Office 2013 Professional Plus - 28
  45. Once you have completed all the above steps you can then deploy this package to your collections and test installation (preferable to a small test group such as yourself first)