Monday, April 07, 2014

PowerShell + SCCM 2012 R2 : Deploy PowerShell Scripts

[UPDATE] Looking for more #PowerShell + #ConfigMgr awesomeness , Check out my compiled list of posts -->

Earlier I blogged about using PowerShell to create an Application from MSI but wait am just warming up to what Configuration Manager has to offer with PowerShell :D
In this post we will be creating an Application having a deployment type which will run PowerShell Scripts (Install / Uninstall). Detect if the Script needs to be run on the Client (using PowerShell code) this is something called Script Detection Method in ConfigMgr.

Let me put this in simple layman terms we will be deploying Scripts to clients and executing them using Configuration Manager Infrastructure.


If you haven't configured Virtual Memory (Page File Size) manually then the System manages it for you. So is the case with all the Systems in my Lab.
So I want to deploy a PS1 Script which will set this (install) and another PS1 Script which will revert this setting to System managed if required (uninstall).

Well that's not all.....just to be clear will be doing all of this using PowerShell (Aah! you knew that already :P !) So let's get the party started <:-p<:-p <:-p <:-p

The Application creation & deployment process will follow the same Steps of :

  1. Creating the Application
  2. Adding a Deployment Type
  3. Distributing the Content to the DP Group
  4. Creating a Collection & Add members to it
  5. Deploy the Application to the Collection

The PowerShell Script:

Well I already have the Script from Technet Gallery which serves my requirement (why re-invent the wheel, right ?? ) It's a good idea to search Technet Gallery for any Scripts before you start, maybe you won't find the exact one but it will serve as a good starting point.

This Script is a PowerShell Module which has 2 functions defined inside it.
Set-OSCVirtualMemory and Set-PageFileSize . 

One needs to test the Scripts before using them in Applications because it may not work sometime. I have tested it locally on my Machine and after knowing that it works am ready to setup an Application and finally deploy it. 

By default the OS manages the PageFile for the System which is evident by the property AutomaticManagedPageFile on WMI Class Win32_ComputerSystem

Below is what I see on my machine right now which means the System manages page file on my machine (DexterDC).

PS> (Get-CimInstance -ClassName Win32_ComputerSystem).AutomaticManagedPageFile

The Script takes above into account disables it and sets the PageFile Size which you specify and also later you can revert back to the setting where System manages it.

This is a cool script and if you are looking for something similar to do in your environment then please go through it , I am sure you will learn quite a few bits from it. I know I did :)

Apart from the Module I need 2 more Scripts to Set the VM size and Unset it. I know I could have copied functions from the Module in these 2 PS1 and gone that way too. But let's stick with the Module Approach. Store all three (2 PS1 + 1 PSM1 files in a Shared path probably where all your Packages go in SCMM )

Second file : Unset_VM_size.ps1

Create Application

Now once we have the Scripts & the Module ready. Let's go ahead with the Application creation. We will go with the same steps which were taken to create a MSI application ....but we will have to specify a lot of information manually here in this case.

Create the Application Object

New-CMApplication -Name "PowerShell Set PageFile" -Description "The Script will set the  Page File Size to Initial 1024 and 2048 maximum" -SoftwareVersion "1.0" -AutoInstall $true 

This will create the Application you can set whole lot of properties on this Object using Set-CMApplication cmdlet. I am gonna move forward. For sake of convenience let me store the Application name in a variable do this:
$Applicationname = "PowerShell Set PageFile"

Now with ConfigMgr 2012 we have various detection methods which detect if the application is already installed. In this case let's define a here-string containing our PS1 code which detects if the Virtual Memory is already configured as per our needs. Before you go on and criticize usage of write-host see this post here .

$scriptDetection = @'
$pagefile = Get-WmiObject -Class Win32_PageFileSetting
$AutomaticManagePageFile = Get-WmiObject -Class Win32_ComputerSystem 
if (($pagefile.InitialSize -eq 1024) -and ($pagefile.MaximumSize -eq 2048) -and ($AutomaticManagePageFile.AutomaticManagedPagefile -eq $false))
    Write-host "Installed"


So now we have a Script code which will detect if the PowerShell Script which we deploy is at all needed to run on the machine to configure the Virtual Memory.

After this let's create the deployment type, it's a very big expression so am gonna use splatting. If you don't know about splatting then you have to do some reading :-B read the about_Splatting article in PowerShell.

Below is my splatting variable  and the Key Values come from the various parameter names in the cmdlet Add-CMDeployment type ...see the comments in the same line too which attempt to explain it.
Note - the above $ScriptDetection variable is used below.

$DeploymentTypeHash = @{
                    ManualSpecifyDeploymentType = $true #Yes we are going to manually specify the Deployment type
                    Applicationname = "$ApplicationName" #Application Name 
                    DeploymentTypeName = "POSH Set PageFile"    #Name given to the Deployment Type
                    DetectDeploymentTypeByCustomScript = $true # Yes deployment type will use a custom script to detect the presence of this 
                    ScriptInstaller = $true # Yes this is a Script Installer
                    ScriptType = 'PowerShell' # yep we will use PowerShell Script
                    ScriptContent =$scriptDetection  # Use the earlier defined here string
                    AdministratorComment = "This will set and reset the VM(pagefile) size" 
                    ContentLocation = "\\dexsccm\Packages\PS1_SetVMSize"  # NAL path to the package
                    InstallationProgram ='powershell.exe -file ".\Set_VM_Size.ps1"'  #Command line to Run for install
                    UninstallProgram ='powershell.exe -file ".\Unset_VM_size.ps1"'  #Command line to Run for un-Install
                    RequiresUserInteraction = $false  #Don't let User interact with this
                    InstallationBehaviorType = 'InstallForSystem' # Targeting Devices here
                    InstallationProgramVisibility = 'Hidden'  # Hide the PowerShell Console

Is it me or these are a lot of parameters /sweat 

Once we have parameters Now time to execute the code...we will use our Splatting variable here:

Add-CMDeploymentType @DeploymentTypeHash 

Now after this you should be seeing a Application created in the ConfigMgr Console and the deployment type too.

We can go ahead and verify all the properties in the ConfigMgr Console but to show why we used here-string above to set up the Script detection method earlier, below is a Screenshot showing the Detection Method for the Deployment Type. 
When we click on the "Edit" button the Script Editor opens up which has our code  B-)

After this let me move this Application under the PowerShell folder. You can read my earlier post on using Folders to organize in SCCM on how this works.

$Application  = Get-CMApplication -Name $Applicationname 
$POSHFolder = Get-CimInstance -ClassName "SMS_ObjectContainerNode" -Filter "Name='PowerShell'" -ComputerName DexSCCM -Namespace root/SMS/Site_DEX -Verbose

Invoke-CimMethod -ClassName SMS_ObjectContainerItem -MethodName MoveMembersEx -Arguments @{InstanceKeys=[string[]]$Application.ModelName;ContainerNodeID=[System.UInt32]0;TargetContainerNodeID=[System.UInt32]($POSHFolder.ContainerNodeID);ObjectTypeName="SMS_ApplicationLatest"} -Namespace root/sms/site_DEX -ComputerName DexSCCM -Verbose
So now we are ready with our Application and the deployment type, let's distribute the content to the Boundary Group in my LAB.

#Distribute the Content to the DP Group
Start-CMContentDistribution -ApplicationName "$ApplicationName" -DistributionPointGroupName "Dex LAB DP group" -Verbose

After this let me create a Device Collection to hold the Members for the deployment of this Application.

#create the Device Collection

New-CMDeviceCollection -Name "$ApplicationName" -Comment "All the Machines where $ApplicationName is sent to" -LimitingCollectionName "All Systems"  -RefreshType Periodic -RefreshSchedule (New-CMSchedule -Start (get-date) -RecurInterval Days -RecurCount 7) 

Now to stress the importance of Folders a bit in ConfigMgr, I had gone and created Folders below the "Device Collections" using PowerShell ;) to reflect the same structure as that under Applications making it easy for me :


You can move the newly created Device Collection under the PowerShell folder too to be consistent with what we are doing.

Enough :D Let's add a member to the Collection and deploy the Application to it.

#Add the Direct Membership Rule to add a Resource as a member to the Collection
Add-CMDeviceCollectionDirectMembershipRule -CollectionName "$ApplicationName"  -Resource (Get-CMDevice -Name "DexterDC") -Verbose

#start the Deployment
Start-CMApplicationDeployment -CollectionName "$ApplicationName" -Name "$ApplicationName" -DeployAction Install -DeployPurpose Available -UserNotification DisplayAll -AvaliableDate (get-date) -AvaliableTime (get-date) -TimeBaseOn LocalTime  -Verbose

After this wait for some time or refresh machine policy on the client (can use Invoke-CMClientNotification cmdlet ) to see the Application appear up in the Software Center.

But even after waiting for a while it doesn't shows up then..... ???

There's a gotcha here if you haven't set the Execution Policy (for PowerShell) in the Client Settings then you would see something like this in the AppDiscovery.log on the Client side.

Bottom line is-->  To use PowerShell Script as a detection method we need to have deployed proper Client Settings on the Clients to set the Execution Policy.

So you should either create a Custom Client settings and deploy it to the Collection with the Members where the Application using Script Detection will be used or change your default client settings (not advised). 
In ConfigMgr 2012 you can create Custom Client Settings and deploy those to Collections. I did blog about doing that with PowerShell too Check it out  Here

So once the appropriate Client settings are deployed to the Client, you need to initiate the Application manager Machine Policy Action so that the Application gets discovered. You can do this with PowerShell to on the Client:

Invoke-CimMethod -ClassName SMS_Client -MethodName TriggerSchedule -Arguments @{sScheduleID='{00000000-0000-0000-0000-000000000121}'}  -Namespace root/ccm

After this you will see in the AppDiscovery.log that the Script Detection works fine and the application shows up in Software Center

Click on Install and it gets installed.

Let's verify that the System doesn't manage Page file anymore and the Page File Settings did get changed.

Note : after Clicking on Install you can see the AppEnforce.log for some interesting entries ;)

That's it for the first part. Will be back with more awesomeness in next one.

/wahaha /bye