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


  1. Dexter,

    I am looking to deploy POSH scripts through the application model in OSD TS, but I am unable to get POSH scripts to deploy.
    I have searched extensively, to no avail.


    Thanks in advance,

    1. Randy,

      Have never laid hands on OSD side of things in ConfigMgr :(
      Maybe you can ask Johan Arwidmark or Kent Agerlund on Twitter about this, someone must be able to guide you.

      Hope it helps.

    2. First of all, you need to set the executionpolicy to unrestricted during the OSD. That must be made with a .cmd script. Run it before installing the application install.
      Something like this:

      @ECHO OFF
      IF EXIST %SystemRoot%\Sysnative\cmd.exe ("%SystemRoot%\Sysnative\cmd.exe" /C "%~dpnx0") & (GOTO end)

      reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell /t REG_DWORD /v EnableScripts /d 1 /f
      reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell /v ExecutionPolicy /d Unrestricted /f
      reg add HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell /t REG_DWORD /v EnableScripts /d 1 /f
      reg add HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell /v ExecutionPolicy /d Unrestricted /f


      After that is done, remember to set the application to install whether or not a user is installed, and also allow it to run within a TS without being deployed.

    3. Thanks Anonymous.
      Hope it helps Randy :)

  2. Regarding policies: you may as well run Powershell.exe -executionpolicy ByPass -File MyWonderfulScript.ps1 to avoid altering the configuration. Or use GPOs instead for enforcing PS script execution Policy.

    1. Yeah ! Good point Sylvain, I overlooked that. Did you try it ?

    2. Wait but in order to use PowerShell script in the detection rule, you will have to set the execution policy in the client settings. As the SCCM Client takes the code and runs on its own (we don't have control over this).

      But for the Application being created ,we can definitely use your suggestion :
      InstallationProgram ='powershell.exe -executionpolicy bypass -file ".\Set_VM_Size.ps1"' #Command line to Run for install
      UninstallProgram ='powershell.exe -executionpolicy bypass -file ".\Unset_VM_size.ps1"' #Command line to Run for un-Install

    3. Indeed the SCCM logic is not familiar to me. Isn't SCCM's identity already enforcing the ExecutionPolicy at runtime ? I believe so, but should be checked. Another way of doing this is a Batch File itself launching PowerShell.exe -executionPolicy blah.
      I'll be working on it in a couple of days, I hope I'll have deeper insights to share.

    4. Yep, I have seen many people do that. But as a heads up you might want to use powershel.exe -file and not the -command while using it in Batch file , if you plan to put error handling in the batch file.

      SCCM now supports using PS code snippets inside for detection rule, it runs the code snippet by dumping it's contents in a file and calling powershell.exe. IMO while it does that it takes the execution policy applied on the SCCM client , haven't checked if it will enforce the machine specific policies.

      Let me know if you find anything.


  3. Hi Dexter,

    I'm trying to create detection rules via powershell. Have no issues with MSI and the automatic detection but need help with Registry type detection rules. Is it possible to use this type of "DeploymentTypeHash" to create a registry detection rule as well?

    Would really appreciate some help with this.


    1. Hi Thomas,
      DeploymentTypeHash is a plain hash table in PowerShell, it is used later for splatting.
      It can be used to create the registry type detection rule too, but obviously the key (parameter) and value (arguments) will change.


    2. Could you give me an example of how to do this?
      I really need help settin/creating Registry-based and manual MSI product code detection rules. It would be nice if you could implement as meny settings as possible in your examples.

      I'm creating a tool for creating and deploying applications

    3. Hi Thomas,
      I do not have an example ready with me at the moment but I worked on something similar with my friend Stephane, he was going to blog about it. Let me ask him.


    4. That sounds good, thanks!

  4. Dexter, this is so PRO, that as veteran, I must salute you. I plan on checking out all your blogs and implementing some of this at LG Electronics in Silicon Valley. I am working at a small RnD facility of there's and this is just awesome stuff to roll out in conjunction with the SCCM 2012 server I built. Have you played around with SCCM 2016 Technical Preview? I am downloading and setting up virtual lab as we speak :)

    1. Gee!!...Thanks Kenneth for the kind words.
      Though I do not work with ConfigMgr anymore but I would love to help you out, if you need it.

      Cheers, have fun exploring as they are adding more and more good stuff to ConfigMgr :)