Monday, May 26, 2014

PowerShell + SCCM 2012 : Create Supersedence Rule

The Application Model in ConfigMgr 2012 let's you do some pretty neat things. One of those things is specifying supersedence.

The ConfigMgr 2012 SDK has an example in C# showing how to create the Supersedence, so I studied it and was able to re-produce it in PowerShell.

Scenario


I have two Notepad++ Applications as below, We will create the Supersedence rule for 6.5.1 making it supersede 6.2.3. Look below for the terminology.




Rest of the Story: code and walkthrough

The code is pretty long and will step through it  in pieces.
Won't be using the ConfigurationManager PowerShell module. This is entirely WMI/CIM and some .NET Fu at work ;)

First let's get the pre-requisites out of the way by doing below:

001
002
003
004
005
006
#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"

I usually prefer setting the $PSDefaultParameterValues while testing out stuff as am lazy to specify the ComputerName and Namespace parameter again and again ;)

Also we need to load the Application Management Dll in our session as we will be dealing with the Class of the objects defined in this assembly.

Now first a little background on what we are going to do, the Application stores all the configurations in it like deployment types, supersedence rules, dependencies etc in the serialized XML  format in the property SDMPackageXML ...to make any changes we need to de-serialize -> make changes to the XML -> Serialize it and push it back up to the WMI Instance of the Application. See very easy.

Read ConfigMgr MVP David O'Brien's article on this for more information

Let's get started and brace yourself for a bumpy ride :

First we get a direct reference to the Application using the [wmi] type accelerator because the SDMPackageXML is a Lazy property (lol am a bit lazy too :P )

After that we deserialize that to get back the XML and then we can manipulate it.

001
002
003
004
005
006
007
008
009
010

#Name of the Application which will be superseded
$Name = "Notepad++ 6.2.3"

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

#deserialize the XML
$supersededDeserializedstuff = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($supersededapplication.SDMPackageXML)

So now we have the deserialized XML , you can feel free to explore it a bit if you like after this (like I did ) and then you will find that you have to add something to the Supersedes property... to show where all the things are gonna be added in the Object , I came up with a neat idea of showing how the final object (Show-Object) will look like :



If the code looks confusing scroll back up and take a look at the object and it will make more sense where it's gonna go.

If we want an application to be superseded then fundamentally what we have to do is prohibit the deployment type of that application to run. This is what we gonna do using $DTDesiredState later (defined below)


001
002
003
# set the Desired State for the Superseded Application's Deployment type to "prohibit" from running
$DTDesiredState = [Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeDesiredState]::Prohibited 

Time to create an intent expression supplying the superseded Application's info to it and whether one need's the superseded Application uninstalled before the new Application (superseding one) installs itself...essentially it is the little check box n the below screenshot:







001
002
003
004
005
006
007
008
009
010
011
012
#Store the arguments before hand
$ApplicationAuthoringScopeId = ($supersededapplication.CI_UniqueID -split "/")[0]
$ApplicationLogicalName = ($supersededapplication.CI_UniqueID -split "/")[1]
$ApplicationVersion =  $supersededapplication.SourceCIVersion
$DeploymentTypeAuthoringScopeId = $supersededDeserializedstuff.DeploymentTypes.scope
$DeploymentTypeLogicalName = $supersededDeserializedstuff.DeploymentTypes.name
$DeploymentTypeVersion = $supersededDeserializedstuff.DeploymentTypes.Version
$uninstall = $false  #this determines if the superseded Application needs to be uninstalled when the new Application is pushed

#create the intent expression
$intentExpression = new-object Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Expressions.DeploymentTypeIntentExpression -ArgumentList  $ApplicationAuthoringScopeId, $ApplicationLogicalName, $ApplicationVersion, $DeploymentTypeAuthoringScopeId, $DeploymentTypeLogicalName, $DeploymentTypeVersion, $DTDesiredState, $uninstall
Note - Deployment Type Desired state stored in $DTDesiredState is used in creation of the intent expression.

After the Expression is created we finally create the DeploymentType Rule which gets added as the supersedence before that we create "none" severity  and empty rule context (one can play with these objects a bit )  which are used to create the rule.

001
002
003
004
005
006
007
008
009
010
011

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

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


#Create the new DeploymentType Rule
$DTRUle = New-Object -TypeName Microsoft.SystemsManagementServer.DesiredConfigurationManagement.Rules.DeploymentTypeRule -ArgumentList $severity, $null, $intentExpression

Our DT Rule is ready now and we need to add this to the superseding application's deserialized XML so , let's first get a direct reference to the Application and :

001
002
003
004
005
006
007
008

$ApplicationName = "Notepad++ 6.5.1"
$application = [wmi](Get-WmiObject -Query "select * from sms_application where LocalizedDisplayName='$ApplicationName' AND ISLatest='true'").__PATH


#Deserialize the SDMPackageXML
$Deserializedstuff = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($application.SDMPackageXML)

We need to add the DT Rule to the superseding Application's deploymenttypes:

001
002
#add the supersedence to the deployment type
$Deserializedstuff.DeploymentTypes[0].Supersedes.Add($DTRUle)

Now everything is in place and we are ready to serialize the XML using below:
001
002
003
# Serialize the XML
$newappxml = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::Serialize($Deserializedstuff, $false)

Now set the property back to the instance of the Application using the infamous put() method ;)


001
002
003
004
005
006
007
#set the property back on the local copy of the Object
$application.SDMPackageXML = $newappxml

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


Verify the Changes in the console :



The code maybe daunting at first but as you test/experiment with it then it starts making a lot more sense..so don't be afraid to play with it.
Awesome...Next topic is dependence rules :)


Am trying to put a list of PowerShell + SCCM blogs out there and do let me know if I missed any.

Friday, May 23, 2014

PowerShell + SCCM 2012 Tip : Get OS Inventory

Another post inspired by the daily job as a ConfigMgr Admin.

Usually while deploying applications as a best practice we check few details about the machine like the Operating System it's running on to ensure that we deploy the correct package targeted to the correct OS.

In past there have been cases where a package targeting XP was send to a Windows 7 machine or vice-versa.

So how do I check OS information :
  1. If Machine is online ;Use PowerShell or any other tool to query WMI to get the info.
  2. If Machine is offline ;Head over to SCCM Reports to see the info

We can use PowerShell to do perform the second part as well because ConfigMgr SMS namespace  provider stores this information in the class named SMS_G_System_Operating_System .

Note - The data in the class SMS_G_System_OPERATING_SYSTEM is stored as a result of the Hardware Inventory of the Win32_OperatingSystem class on a remote machine. So if the H/W Inventory has not run then this data is not available.

One can see the class and the properties on it using Get-WMIObject or Get-CIMInstance cmdlet like below:
PS C:\> Get-WmiObject -Class SMS_G_System_OPERATING_SYSTEM -Namespace Root/SMS/site_DEX -List -ComputerName DexSCCM


   NameSpace: ROOT\SMS\site_DEX

Name                                Methods              Properties
----                                -------              ----------
SMS_G_System_OPERATING_SYSTEM       {}                   {BootDevice, BuildNumber, Caption, CountryCode...}


PS C:\> Get-WmiObject -Class SMS_G_System_OPERATING_SYSTEM -Namespace Root/SMS/site_DEX -List -ComputerName DexSCCM| select -ExpandProperty properties | select Name

Name
----
BootDevice
BuildNumber
Caption
CountryCode
CSDVersion
Description
FreePhysicalMemory
FreeVirtualMemory
GroupID
InstallDate
LastBootUpTime
Locale
Manufacturer
MaxNumberOfProcesses
Name
Organization
OSLanguage
RegisteredUser
ResourceID
RevisionID
SystemDirectory
TimeStamp
TotalSwapSpaceSize
TotalVirtualMemorySize
TotalVisibleMemorySize
Version
WindowsDirectory



Note - One needs to pass the SCCM Server name to -ComputerName parameter and to -NameSpace parameter the correct namespace e.g. Root/SMS/Site_DEX (where DEX is the 3 letter site code)

So utilizing this information I wrote a function  Get-OSInfo  which works in the below order :

  • Check If Machine is online and the User running the Script has access to query WMI on the remote machine : If Yes then query Remote Machine for the OS information
  • If the Machine is offline or the User is denied access to query WMI on the remote machine then we query SCCM Server for the same.
Make a note of the property named PSComputername on the object written to pipeline this tell where the information came from (Remote machine or ConfigMgr Server).

Usage:
PS E:\> Get-OSinfofromSCCM -ComputerName dexterdc -Verbose
VERBOSE: [BEGIN]
VERBOSE: [PROCESS] Querying dexterdc for OSInfo


PSComputerName  : dexterdc
ComputerName    : dexterdc
OS              : Microsoft Windows Server 2012 R2 Datacenter Preview
ServicePack     : 
LastBootupTime  : 5/23/2014 6:06:05 AM
InstallDate     : 1/18/2014 3:29:37 PM
OSVersion       : 6.3.9431
SystemDirectory : C:\Windows\system32

VERBOSE: [END]



PS E:\> Get-OSinfofromSCCM -ComputerName dexterdc -SCCMServer dexsccm 


PSComputerName  : dexterdc
ComputerName    : dexterdc
OS              : Microsoft Windows Server 2012 R2 Datacenter Preview
ServicePack     : 
LastBootupTime  : 5/23/2014 6:06:05 AM
InstallDate     : 1/18/2014 3:29:37 PM
OSVersion       : 6.3.9431
SystemDirectory : C:\Windows\system32




PS E:\> Get-OSinfofromSCCM -ComputerName dexterdc -SCCMServer dexsccm -QuerySCCMOnly -Verbose
VERBOSE: [BEGIN]
VERBOSE: SCCM Server was supplied as an argument ...trying to open a CIM Session
VERBOSE: [BEGIN] WSMAN is responsive
VERBOSE: Operation '' complete.
VERBOSE: [BEGIN] [WSMAN] CIM SESSION - Opened
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = select * from SMS_ProviderLocation where ProviderForLocalSite = true,
'queryDialect' = WQL,'namespaceName' = root\sms'.
VERBOSE: Operation 'Query CimInstances' complete.
VERBOSE: [BEGIN] Provider is located on DexSCCM.dexter.com in namespace root\sms\site_DEX
VERBOSE: [PROCESS] Querying dexsccm for OSInfo
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = Select Version,CSDVersion,SystemDirectory,Installdate,LastBootuptime,
installdate,caption,Description from SMS_G_System_OPERATING_SYSTEM JOIN SMS_R_System ON SMS_R_System.ResourceID = SMS_G_System_OPERATING_SYSTEM.ResourceID where SMS_
R_System.NetbiosName='dexterdc','queryDialect' = WQL,'namespaceName' = root\sms\site_DEX'.
VERBOSE: Operation 'Query CimInstances' complete.


PSComputerName  : dexsccm
ComputerName    : dexterdc
OS              : Microsoft Windows Server 2012 R2 Datacenter Preview
ServicePack     : 
LastBootupTime  : 5/22/2014 4:24:50 PM
InstallDate     : 1/18/2014 8:59:37 PM
OSVersion       : 6.3.9431
SystemDirectory : C:\Windows\system32

VERBOSE: [END] Removing the CIM Session
VERBOSE: [END]

The code is self explanatory and there is a cool trick where I explicitly thow an exception to get inside catch block where nested try-catch block is used again.
Also have a look at the WQL query used to get the info from the ConfigMgr Server. Pretty neat ! :-B

There are a lot of Hardware inventory classes in ConfigMgr which can be used by us to inventory our environment and this should serve as a base to build upon :)

That's it for the post. If you have any suggestions or feedback please do drop a comment.


Below is the script link and gist:
http://gallery.technet.microsoft.com/Get-OS-Info-from-SCCM-d4e2858a

Tuesday, May 20, 2014

PowerShell + SCCM Tip: Get MachineName for a User

While doing Application deployments , we have cases where the User forgets to specify the Machine Name to which a software needs to be deployed to.

We use Query-Based Deployment rules in ConfigMgr where we add the machine names to a Collection named after the Application..have already automated this process ;)

But if the machine name is not specified then we either:

  1. Get the Machine name from SCCM Reports if available.
  2. Drop an email to the Requester for the same
For getting the machine name for a User in ConfigMgr 07 we use the Reports obviously :D


The report is located under "Users" node in ConfigMgr Reports page.

But actually this can be retrieved using WMI/CIM too.

The Class which has this info is SMS_R_System under namespace Root/SMS/site_<SiteCode> and let's take a look at it.

Note: Before going further I have already set the ComputerName parameter and namespace parameter in the $PSDefaultParameters. Below is how you do it in PS Console.
$PSDefaultParameterValues = @{"Get-WMIObject:Namespace"="Root/SMS/site_DEX";"Get-WMIObject:ComputerName"="DexSCCM";"Get-CIMCLass:Namespace"="Root/SMS/site_DEX";"Get-CImClass:ComputerName"="DexSCCM"}

Now let's look at the properties of the class we are interested in. 




Now let's use the Get-WMIObject or Get-CIMInstance to get a machine name for a user "Administrator"

Note doing this in my Lab so just going ahead with the User "Administrator". This works for both ConfigMgr 07 and 12 that's the beauty of using WMI ;)

Get-CimInstance -Query "Select NetbiosName from SMS_R_System where lastlogonusername='Administrator'"
You will notice that running the above will only return back the netbiosname property...doing this as it is the only information am after right now.

For the above WMI/CIM query we need to know the SamAccountName of the User, so suppose we have a User Named "Dexter POSH" then we could leverage ADSI here to filter out the User's matching the name.

So I have written a Function named Get-SCCMUserComputer which does all this.

After you have loaded the function then you can run it like below to pass samaccountnames to the -Identity parameter
Get-SCCMUserComputer -identity administrator -Verbose
VERBOSE: [BEGIN] WSMAN is responsive
VERBOSE: Operation '' complete.
VERBOSE: [BEGIN] [WSMAN] CIM SESSION - Opened
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = select * from SMS_ProviderLocation where ProviderForLocalSite = true,
'queryDialect' = WQL,'namespaceName' = root\sms'.
VERBOSE: Operation 'Query CimInstances' complete.
VERBOSE: [BEGIN] Provider is located on DexSCCM.dexter.com in namespace root\sms\site_DEX
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = Select NetbiosName from SMS_R_System where LastlogonUserName='adminis
trator','queryDialect' = WQL,'namespaceName' = root\sms\site_DEX'.

SamAccountName                                                                     ComputerName                                                                     
--------------                                                                     ------------                                                                     
administrator                                                                      DEXTERDC                                                                         
VERBOSE: Operation 'Query CimInstances' complete.
VERBOSE: [END] Ending the Function

Now there is another parameter by the name -Name :P
So what it does is it uses ADSI to search the domain for matching users and prompts you to select one out of those to move forward. Let me show that real quick so if I invoke the function like below:
Get-SCCMUserComputer -Name Admin -Verbose

Now the Script will use "*Admin*" to search the AD using ADSI and ask you to select the Users (multiple Users can be selected... :) )you want to get the netbiosname for..below is a screenshot showing this:




Only other thing worth mentioning is that when using the -Name parameter how the ADSI filter is created.  If you specify a Name without any spaces then an asterix is added to both start and end to filter out using ADSI.
For Ex - In above example "Admin" was specified and the filter used is "*Admin*"

But if I specify an argument "Dexter POSH" then the filter used is "Dexter*POSH" I did this because if someone is specifying the name in this way then he probably knows what is the first and last name he is searching for.

Not saying the best way but am going ahead with this....any inputs are welcomed.


NOTE -- Sometimes this information might not be populated there in SCCM so if this is not available then one needs to drop an email asking the User for the Machinename.

Hope this saves a few Mouse clicks for someone.
This is it for today's post.

Below is the Gist and Technet Script Link:

http://gallery.technet.microsoft.com/Get-Machine-Name-of-a-User-a6617e46