Saturday, June 21, 2014

PowerShell + SCCM 2012 : Get Started with CM Cmdlets

This post will quickly cover on how to start using PowerShell with ConfigMgr. It's good to see that ConfigMgr Admins are finally embracing the Shell :) 

Planning to have one of these getting started hangouts for PowerShell Bangalore User Group (@PSBUG) in near future.

There are essentially two routes:
  1. Using ConfigurationManager Cmdlets
  2. Using WMI/CIM (next post probably)

Using ConfigurationManager Cmdlets


ConfigMgr starting from 2012 SP1 has got the official PowerShell support, which means when you install the ConfigMgr console on a machine then you will get a PowerShell Module along with it in the below location :



<drive>\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin


Don't worry you don't have to remember this. There is an environment variable SMS_ADMIN_UI_PATH which holds this piece of information for you (Note the path we need is till bin folder only ) 



The best way to get the CM cmdlets (the cmdlets are prefixed with CM) is to open the the ConfigMgr Console and click on the top left "Connect via Windows PowerShell"



After that you should get a prompt like below if you are doing this the first time:

Select "A" (Always Run) to trust the MSFT Code Signing Cert for the User. 


Accepting the above creates MRU key for the User , see below is the MRU key created for me :




After the first run (trusting the Code Signing Cert from MSFT) one can use the normal PowerShell console to load the Module in below way :

001
002
003
#Load the ConfigurationManager Module
Import-Module -Name "$(split-path $Env:SMS_ADMIN_UI_PATH)\ConfigurationManager.psd1"

Now this has been fixed in ConfigMgr 2012 R2 CU1 . Read this question at Technet for more details.

One can also install the certificate for the local machine (for different Automation scenarios) using the below PowerShell function by Tore Groneng

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
function Import-SCCMmoduleCert
{
<#
.Synopsis
   Imports the signed certificate used in the SCCM module into the certificate store on the local computer
.DESCRIPTION
   Requires administrative privileges to run. Run the function as the user that will have the cert
   imported. The function accepts no parameters and does not return any output.
.EXAMPLE
   Import-SCCMmoduleCert
.NOTES
    Created by Tore Groneng @ToreGroneng Tore.Groneng@gmail.com

#>

[CmdletBinding()]
[OutputType([int])]
Param()

    Write-verbose "Start $($MyInvocation.MyCommand.Name)"
    $sccmModulePath = "$(Split-Path $env:SMS_ADMIN_UI_PATH -Parent)\ConfigurationManager.psd1"

    Write-Verbose "Module path is $sccmModulePath, getting cert"
    $cert = Get-AuthenticodeSignature -FilePath "$sccmModulePath" -ErrorAction SilentlyContinue

    Write-Verbose "Creating a store object for LocalMachine\TrustedPublisher"
    $store = new-object System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher","LocalMachine")
    $store.Open("MaxAllowed")

    Write-Verbose "Adding cert to store"
    $store.Add($cert.SignerCertificate)
    $store.Close()

    Write-Verbose "Done"
}


Now the whole CM Cmdlets are there for you to explore. All the PowerShell concepts apply here with only gotcha --> one has to Set the present location to the CMSite Drive before using the CM cmdlets .


In the next post will cover on how to use PowerShell & WMI  (ConfigMgr Context)

Below is an animated GIF to show this in action:







Resources :

ConfigMgr Scripting With PowerShell Module - Tore Groneng
http://asaconsultant.blogspot.no/2014/05/configmgr-scripting-with-powershell.html

How to Use Scripts with ConfigMgr PowerShell cmdlets - David O'Brien
http://www.david-obrien.net/2014/05/22/use-scripts-configmgr-powershell-cmdlets/#


Tuesday, June 17, 2014

PowerShell + SCCM 2012 : Automate Patching

This is an evolving post and is related to a project aiming to automate Software Updates in our environment. 

The environment is a little complex as we have to work with other teams to get the list of Bulletin-IDs of S/W Updates (or patches) which will be deployed to our environment. So we can't probably go for ADR in CM 2012.

So we will look at automating this Patching scenario from the very beginning where we get a list of Bulletin-ID's from outside source.

Note - This post is inspired by Steve Rachui's post on Automating Software Updates, where he has shown some very cool stuff (link at the end check that one out).
Thanks to Stephane >:D< for sharing his WMI Functions which helped me a lot, along with cool B-) people like Kaido,Peter & David who have shared there awesome work :). All the resources link are at the end of the page.

Below are the steps we will follow, which should get someone started who is looking to achieve the same:

  1. Get list of patches to be deployed
  2. Create a Software Update Group
  3. Download the Software Updates
  4. Create  deployment Package
  5. Deploy them
Note - that this is just groundwork for my project....so finer details like logging, error handling will be taken care of later.  I went with the WMI way of doing this as encountered that the cmdlets shipped with CM are little buggy right now but if it suits you go ahead and try those too.

1. Get list (CI_ID) of Patches to be deployed

This is the first and it's important to put in the logic to only select the patches you are looking for.
There is a patchList.txt file containing the approved Bulletin-ID's which are to be deployed to the Machines.


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
# Use Splatting
$hash = @{
            #open a CIM sesison to the ConfigMgr Server
            cimsession = New-CimSession -ComputerName dexsccm
            NameSpace = 'Root\SMS\Site_DEX' #point to the SMS Namespace
            ErrorAction = 'Stop'

        }

$BulletinIDList = get-content C:\temp\BulletinIDList.txt

# loop through the contents of the text file to build you a query
for ( $i = 0 ; $i -lt $BulletinIDList.count;$i++)
{
    if ($i -eq 0)
    {
        $query = "Select * from SMS_SoftwareUpdate WHERE (BulletinID='$($BulletinIDList[$i])')"
    }
    else
    {
        $query = $query + " OR (BulletinID='$($BulletinIDList[$i])')"

    }
}

#Get the Software Updates corresponding to the Bulletin-IDs
$BulletinIDPatches  = Get-CimInstance -Query $query @hash

Now we have list of all the S/W Updates as per the Bulletin-ID's , we need to filter out all the Software Updates applicable to my Environment (NumMissing property tells me on how many machines it is missing)


001
002
#Filter out not Applicable patches
$BulletinIDPatches = $BulletinIDPatches | where NumMissing -ne 0

One can also filter on a lot of parameters here like the Product, OS etc.
For my environment want to only select the patches applicable to Server OS (server 2008 R2 & Server 2012 R2 )


001
002
#Select Products you are targeting
$productcategories = Get-CimInstance -ClassName  SMS_UpdateCategoryInstance -Filter 'CategoryTypeName="Product"' | Out-GridView -Title "Slect the Products to target" -PassThru

Above will give an out-gridview  window for me to select the products am targeting...I will select Windows Server 2008 and Windows Server 2012 & 2012 R2



Now once I have the Products selected I will filter out all the earlier Software Updates I have that apply on these Products :

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
# will use a HashTable for filtering later
$ProductIDHash = @{}

#create a Hash Table used to filter out later
$productcategories | ForEach-Object -Process {$ProductIDHash.Add("$($($_.CategoryInstance_UniqueID) -replace 'Product:')","$($_.LocalizedCategoryInstanceName)")}

#Filter out S/W updates which do not apply to the Products we want
 $DeployPatches = $BulletinIDPatches |
    ForEach-Object -Process {
         # Check if the Software Update is applicable to the list of Products we selected and doesn't target Itanium Architecture
         if ( $ProductIDHash.Contains($([System.Xml.XmlDocument]$_.ApplicabilityCondition).ApplicabilityRule.ProductId) -and ( $_.LocalizedDisplayName -notlike "*Itanium*") )
         {
            # Adding a note property to the S/W update Object in Pipeline to know which product it applies to
            $_ | Add-Member -MemberType NoteProperty -Name AppliesTo -Value "$($ProductIDHash.$($([System.Xml.XmlDocument]$_.ApplicabilityCondition).ApplicabilityRule.ProductId))"

            Write-Output -InputObject $_
         }
    }

Now let's see what is there in the $DeployPatches which is our Final list of S/W updates which are going to be deployed:
PS> $DeployPatches | format-list -Property localizeddisplayname,appliesto


localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957503)
AppliesTo            : Windows Server 2008 R2

localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957189)
AppliesTo            : Windows Server 2008 R2

localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2939576)
AppliesTo            : Windows Server 2008 R2

localizeddisplayname : Cumulative Security Update for Internet Explorer 10 for Windows Server 2008 R2 Service Pack 1
                       for x64-based Systems (KB2957689)
AppliesTo            : Windows Server 2008 R2

localizeddisplayname : Security Update for Windows Server 2008 R2 x64 Edition (KB2957509)
AppliesTo            : Windows Server 2008 R2




Note that In order to Script the entire solution we will have to work with CI_ID as the SDK documentation states :

The CI_ID value identifies the software updates information across several classes. For the purposes of using the Configuration Manager SDK interfaces, the CI_ID value is vital


2. Create a Software Update Group

There is a cmdlet in the Configuration Manager Module by the name New-CMSoftwareUpdateGroup , but I wanted to stick to the WMI way of doing this.
We have to create an instance of the Class SMS_AuthorizationList and for this we will have to switch back to WMI ....can't be done using CIM :(


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
#region create Software Update Group (SMS_AuthorizationList)

#prepare the Default Paramater Values to work with Get-WMIObject
$PSDefaultParameterValues =@{"get-wmiobject:namespace"="Root\SMS\site_DEX";"get-WMIObject:computername"="DexSCCM"}

#Get a reference to the WMI Class SMS_CI_LocalizedProperties
$class = Get-WmiObject -Class SMS_CI_LocalizedProperties -list

#instantiate the Class
$LocalizedProperties = $class.CreateInstance()

$LocalizedProperties.DisplayName="TEST Software Update Group"
$LocalizedProperties.Description="Testing PS Automation"


$class = Get-WmiObject -Class SMS_AuthorizationList -list
$UpdateGroup = $class.CreateInstance()

$UpdateGroup.LocalizedInformation = $LocalizedProperties
$UpdateGroup.Updates = $DeployPatches | select -ExpandProperty CI_ID

$UpdateGroup.put()
$UpdateGroup.get()
#endregion create Software Update Group (SMS_AuthorizationList)

There is an post by David O'Brien which covers this very topic...link at the end.


So after this if you head to the ConfigMgr Console you will see the Update Group created along with the appropriate Updates inside that.




Software Updates inside above Update Group


Note - Compare this with the PowerShell Console Output of $DeployPatches | format-list -Property localizeddisplayname,appliesto

3. Download the Software Updates

Now need to download all the Software Updates in a temporary location, we do this in a UNC path. So we create a new PSDrive pointing to that location. Then we use the relation between the classes SMS_SoftwareUpdate , SMS_CIToContent and SMS_CIContentFiles described in the SDK to get the URL for the download.

Then later BITS is used to download the patches..right now I am doing this in my own LAB and will try this later in the QA  and add support to handle failed downloads cause of network bandwidth costs.

Will be using the ContentID to create a folder where the corresponding patch will be downloaded. You will see in next section why I need to do this.



001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
#region Download the Software Updates

#Create a new PSDrive where the Patches will be downloaded
New-PSDrive -Name M -PSProvider FileSystem -Root "\\dexsccm\patches\2014"
$downloadpath = "M:"

$DownloadInfo =foreach ($CI_ID in  $UpdateGroup.Updates)
{
    $contentID = Get-CimInstance -Query "Select ContentID,ContentUniqueID,ContentLocales from SMS_CITOContent Where CI_ID='$CI_ID'"  @hash
    #Filter out the English Local and ContentID's not targeted to a particular Language
    $contentID = $contentID  | Where {($_.ContentLocales -eq "Locale:9"-or ($_.ContentLocales -eq "Locale:0") }

    foreach ($id in $contentID)
    {
        $contentFile = Get-CimInstance -Query "Select FileName,SourceURL from SMS_CIContentfiles WHERE ContentID='$($ID.ContentID)'" @hash
        [pscustomobject]@{Source = $contentFile.SourceURL ;
                            Destination = "$downloadpath\$($id.ContentID)\$($contentFile.FileName)";
                             }

    }
}

#test and create the Destination Folders if needed
$DownloadInfo.destination |
    ForEach-Object -Process {
            If (! (test-path -Path "filesystem::$(Split-Path -Path $_)"))
             {
                New-Item -ItemType directory -Path "$(Split-Path -Path $_)"
            }
        }

$DownloadInfo | Start-BitsTransfer
#endregion Download the Software Updates


Now the the UNC path where am storing all the downloaded patches looks like below :


Note - There are caveats around running the BitsTransfer as a job. I am testing it and once done will post it

4. Create Deployment Package

Need to create the deployment package which will eventually get distributed to the Distribution Points and then  deployed to the Clients.


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
#region Create the Deployment Package

#Get the Class
$class = Get-WmiObject -Class SMS_SoftwareUpdatesPackage -List

#Instantiate the Class Object
$DeployPackage = $class.CreateInstance()

#set the appropriate properties on the Instance
$DeployPackage.Name = "Test Deploy Package"
$DeployPackage.SourceSite = "DEX"
$DeployPackage.PkgSourcePath = "\\dexsccm\patches\2014"
$DeployPackage.Description = "testing PS Automation"
$DeployPackage.PkgSourceFlag = [int32]2 #Value of 2 -->Stores Software Updates

#persist the changes
$DeployPackage.put()

#get the latest WMI Instance back
$DeployPackage.get()

#endregion Create the Deployment Package

The deployment package is created and now to tell it to where to get/add the patches from we will invoke a method on the WMI Object.

Using the old trick of asking PowerShell the Method Overloaddefinition (where I just dot reference the Method name) , you will get below:
PS> $DeployPackage.AddUpdateContent

OverloadDefinitions
-------------------
System.Management.ManagementBaseObject AddUpdateContent(System.UInt32[] ContentIDs, System.String[] ContentSourcePath,
System.Boolean bRefreshDPs)


So it tells need to pass an array of ContentIDs this deployment package will store and then the array of where these are stored (note it's an array) and then a Boolean value telling if we want to refresh DP's.

Below is how we can get this information. Note how using the ContenID as the folder name helps here to get the information we need:
001
002
003
004
005
006
#get the Array of content source path
$contentsourcepath = Get-ChildItem  -path 'M:' | select -ExpandProperty Fullname

#get the array of ContentIDs
$allContentIDs =  $contentsourcepath | foreach {Split-Path  -Path $_ -Leaf}

Make sure that the number of elements in both the arrays are same ;)
Below is what it should be like:



Now ready to invoke the method :

001
002
003
# call the Method...May the force be with me ;)
$DeployPackage.AddUpdateContent($allContentIDs,$contentsourcepath,$true)
If you have done everything correct this should give you something like below:



Meanwhile if you get any error or even success I urge you to take a look at the SMSProv.log which is your best friend if you are looking to automate ConfigMgr.



After all is done you can verify the same in the console too :

Deploy Package is created :

Have a look inside the Deployment Package...Updates do reflect:



After all is done if we take a look back to the UNC path :



It needs cleanup period:


001
002
003
#final cleanup
$DownloadInfo.destination | ForEach-Object -Process { Remove-Item -Path (split-path -path $_-Recurse -verbose}       

5. Deploy them

Leaving this for now one can achieve this either using WMI or the Cmdlet.

CM Cmdlet is  Start-CMSoftwareUpdateDeployment.
The WMI Class you would be interested in is SMS_UpdatesAssignment

Go ahead explore & automate <:-P

========================================================================
[UPDATE]
I am trying this in my environment and learning the challenges faced as I progress. The process am following for this might not be the prefect one but I would love to take any inputs/feedback.

P.S. - Have a very cool & awesome idea in my mind but will post about it when it's done.

========================================================================
Resources :

Automating Software Updates - Steve Rachui
http://blogs.msdn.com/b/steverac/archive/2014/06/12/automating-software-updates.aspx

S/W Update WMI Functions - Stephane Van Gulick
http://powershelldistrict.com/?p=340

Add Update Content to a deployment Package - Peter van der Woude
www.petervanderwoude.nl/post/add-update-content-to-a-deployment-package-via-powershell-in-configmgr-2012/

Create a New Software update group in ConfigMgr - David O'Brien
http://www.david-obrien.net/2012/12/02/create-a-new-software-update-group-in-configmgr/

ConfigMgr PowerShell SDK - Kaido
http://cm12sdk.net/?page_id=10

Sunday, June 08, 2014

Tip - Add Syntax highlighting for PowerShell Code

There are lot of ways to add syntax highlighting to the PowerShell code in your blog, see the post by MVP Francios Xavier-Cat.

I just happened to come across one more which is pretty easy to use and best part is it uses a PowerShell module to do so.


Follow the below steps :
  1. Download the Copy to Colorized HTML module and put the extracted copy in your Modules directory.
  2. Add the below code to your ISE Profile, which will add a submenu and the "Ctrl+Shift+C" shortcut to copy the HTML content in your clipboard. 

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add(“Copy As Colorized HTML”,{Copy-ColorizedHTML},“Ctrl+Shift+C”| Out-Null


The module has a single cmdlet called "Copy-ColorizedHTML" which copies the HTML content in your clipboard for use.

Once the content is copied to your clipboard just paste it to your blog.

NOTE - This method can also be used to while authoring an article for Technet Wiki.

see the animated GIF below:




Enjoy !



Wednesday, June 04, 2014

PowerShell + SCCM 2012 : Create Dependency Rules

Following on my last post on creating supersedence rules in ConfigMgr Applications with PowerShell, this post is all about creating dependency rules.

The approach is very similar to the last one and Adam Meltzer had already posted the sample C# version of the code here .

Have to admit I tried to re-use the code from the last time and in the Process screwed one of the Applications (by putting up wrong serialized XML on the WMI Instance of the App).

There can be multiple dependencies in ConfigMgr (attached with a OR or AND operator), but for the sake of simplicity we will look at a very simple scenario  :


Scenario:



For Application 1 ( Notepad++ in this case) we will add an Application 2 (e.g .NET 4 ) as the dependency. I know that hardly is a dependency but I am a bit lazy to create other applications at the moment...though I can do that with #PowerShell too..but am too lazy for that too :P 

At the end of the Code we will get this relationship:





Code and Walkthrough:

[UPDATE] fixed few typos in the code, tested it again in my environment it does work.

To be entirely honest I reverse-engineered this one as there were too many things to look at. 

Created a dependency on some other random application (from Console) and then got the de-serialized Object back, studied it and worked my way through the changes needed.

Note - the code about to follow doesn't require any PowerShell Module but however is dependent on few assemblies (can be found on a machine having ConfigMgr Console installed).

First let put the pre-requisites out of the way:




#Load the Default Parameter Values for Get-WMIObject cmdlet
$PSDefaultParameterValues =@{"Get-wmiobject:namespace"="Root\SMS\site_DEX";"Get-WMIObject:computername"="DexSCCM"}

#load the Application Management DLL
Add-Type -Path "$(Split-Path $Env:SMS_ADMIN_UI_PATH)\Microsoft.ConfigurationManagement.ApplicationManagement.dll"
Add-Type -Path "$(Split-Path $Env:SMS_ADMIN_UI_PATH)\Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll"

Note here one extra dll is referenced the second one ending with MsiInstaller.dll

Now we will try to get the direct reference to the Applications Objects which will be used later on :



#Creating Type Accelerators - for making assemlby refrences easier later
$accelerators = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
$accelerators::Add('SccmSerializer',[type]'Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer')


#region Application 1
$ApplicationName1 = "Notepad++ 6.2.3"

#get direct reference to the Application's WMI Instance
$application1 = [wmi](Get-WmiObject -Query "select * from sms_application where LocalizedDisplayName='$ApplicationName1' AND ISLatest='true'").__PATH

#Deserialize the SDMPackageXML
$App1Deserializedstuff = [SccmSerializer]::DeserializeFromString($application1.SDMPackageXML)

#endregion Application 1


#region Application 2

#Name of the Application which will be added as a dependency
$ApplicationName2 = ".NET4"

#Reference to the above application
$application2 = [wmi](Get-WmiObject -Query "select * from sms_application where LocalizedDisplayName='$ApplicationName2' AND ISLatest='true'").__PATH

#deserialize the XML
$App2Deserializedstuff = [SccmSerializer]::DeserializeFromString($application2.SDMPackageXML)

#endregion

One another neat trick which we can use here is that of creating type accelerators. Note that I create a TA 'SccmSerializer' at the firest 3 lines (above code) to save me typing up that long typename again and again.

Now while creating Dependency we do things a little bit differently then what was done with Supersedence...not a surprise there :D 

Below is the Object hierarchy depicting where all the changes go :



Object Figure #1
Now all the things will be added inside this Dependencies property, So before we start another Object view showing things we will configure in the code :



Object Figure #2

In short we will create an Deployment Type Rule  using  Expression, Annotation,  Severity and an empty Rule Context.

Creating Expression is the real piece of work here and this is where we will start first , Have put the few numbers in some order to follow along:


Object Figure #3
The below code will create a Deployment Type expression and the code is in order corresponding to the numbers in Object Figure #3:



  • Code below for Region 1: Store all the Arguments before hand



#region 1

#Store the arguments before hand
$ApplicationAuthoringScopeId = ($application2.CI_UniqueID -split "/")[0]
$ApplicationLogicalName = ($application2.CI_UniqueID -split "/")[1]
$ApplicationVersion =  $application2.SourceCIVersion
$DeploymentTypeAuthoringScopeId = $App2Deserializedstuff.DeploymentTypes.scope
$DeploymentTypeLogicalName = $App2Deserializedstuff.DeploymentTypes.name
$DeploymentTypeVersion = $App2Deserializedstuff.DeploymentTypes.Version
$EnforceDesiredState = $True  #this determines if the dependency Application will be auto installed

#endregion 1


  • Code Below for Region 2: Desired State of the dependency is Mandatory / Required



#region 2
# set the Desired State as "Required" or Mandatory
$DTDesiredState = [Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeDesiredState]::Required

#endregion 2

  • Code Below Region 3: Create an Intent Expression and add it to Operand
    Note - The Operand is a custom collection of the Intent Type Expressions ;)


#region 3
#create the intent expression which will be addded to the Operand
$intentExpression = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeIntentExpression -ArgumentList  $ApplicationAuthoringScopeId, $ApplicationLogicalName, $ApplicationVersion, $DeploymentTypeAuthoringScopeId, $DeploymentTypeLogicalName, $DeploymentTypeVersion, $DTDesiredState, $AutoInstall

#Create the Operand - Note the typename of this one
$operand = New-Object  Microsoft.ConfigurationManagement.DesiredConfigurationManagement.CustomCollection[Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeIntentExpression]

#add the Intent Expression to the Operand
$operand.Add($intentExpression)

#endregion 3

  • Code Below Region 4: Create an OR operator which can be used later to combine several Operands. 

#region 4

#create the new OR operator
$OrOperator = [Microsoft.ConfigurationManagement.DesiredConfigurationManagement.ExpressionOperators.ExpressionOperator]::Or

#endregion 4
  • Code Below Region 5 : use Operator and Operand to create the Expression

#region 5

#Now the Operator and Operand are added to the Expression
$BaseExpression = New-Object -TypeName Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeExpression -ArgumentList $OrOperator,$operand

#endregion 5

Voila our expression is ready :)

Moving on to the Object Figure #2
We now need to create Severity, Annotation and an empty Rule Context (will suffice).
Annotation is nothing but a name given to the Dependency Rule andis a must for Dependency Rules (try skipping that)



# Create the Severity Critical
$severity = [Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.NoncomplianceSeverity]::Critical

# Create the Empty Rule Context
$RuleContext = New-Object -TypeName Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.RuleScope

#Create the Annotation - Name & description of the Dependency
$annotation = New-Object -TypeName Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.Annotation
$annotation.DisplayName.Text = "DependencyName"


Putting Everything Together

So we have all the ingredients ready now to finally create the Deployment Rule and add it to the Dependencies property (Object Figure #1)



#Create the new DeploymentType Rule
$DTRUle = New-Object -TypeName Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.DeploymentTypeRule -ArgumentList $("DTRule_"+[guid]::NewGuid().Guid),$severity, $annotation, $BaseExpression


#add the DepolymentType Rule to Dependecies
$App1Deserializedstuff.DeploymentTypes[0].Dependencies.Add($DTRUle)


Now the Deployment Type Rule is added to the de-serialized XML and we are now ready to serialize it and push it to the WMI Instance of the Application.




# Serialize the XML
$newappxml = [SccmSerializer]::Serialize($App1Deserializedstuff, $false)

#set the property back on the local copy of the Object
$application1.SDMPackageXML = $newappxml

#Now time to set the changes back to the ConfigMgr
$application1.Put()

In the last line when you try to update the WMI Instance of the Application you need to acquire a SEDO lock on the Application Object. Make sure that no one is editing the Application at that point of time.

Proof :

Will go back to my Room (LAB) and attach the Screenshot later.

As promised :




Thanks that's it for today. What's next ? Requirement Rules
Cya