Browsed by
Category: SCCM

Automated updates of Devolution’s Remote Desktop Manager using SCCM

Automated updates of Devolution’s Remote Desktop Manager using SCCM

Recently I have been spending time going back to basic’s around automation in SCCM with PowerShell to brush up on my skills. As part of this I spent a few days playing around with migrating to use Visual Studio as my development space for PowerShell with Adam Driscoll’s PowerShell Tools for Visual Studio add in, which I can’t recommend highly enough when tied to TFS online, Version becomes a super simple task of checking your code in rather than saving different versions.

So I have my Dev space all setup, and went to connect to my Lab Servers Via Devolutions Remote Desktop Manager and got this Prompt

Which annoyed me to no end as I only installed it 3 weeks ago and it appears there had already been 2 version changes. So rather than just disabling the prompt to check for updates as I would normally do on RDM, I started investigating how I can auto mate the updates of RDM without paying for a Third Party tool, and still have the latest version available from my SCCM Server.

So first things first we need to find out how the Application checks to see if there are any updated versions available, for this we can use Fiddler which is a great tool that allows to capture the Http/Https traffic leaving your computer.

Which presented us with these links:

http://remotedesktopmanager.com/products.htm

http://remotedesktopmanager.com/data/RdmChangeHistoryUpdate.htm

From here we browse to each and can see that http://remotedesktopmanager.com/products.htm returns this:

Which we can obtain the current version for each of the versions of RDM, along with where to download the application exe file from. Right about now I was thinking this is going to be a cake walk we can just step through each of the lines in the htm file and select only the ones which we need.

So we grab the web address and use the following script to obtain the htm page into a PowerShell Variable:

$wc = New-Object Net.WebClient

$srdm = $wc.DownloadString(“http://remotedesktopmanager.com/products.htm”)

And when we run $srdm in the ISE to see the list is looks like this:

This should be super easy, I’m still thinking we can use a simple foreach on $srdm like I would on a normal multi line string. I try it, and I get the all of the information back, which I must say threw me for a minute, so I tried

$srdm.count

With the following result:

I dig deeper into $srdm and find that the end of the line is a line break rather than a carriage Return as I was expecting, so after some quick research found that we can split $srdm using $srdm.Split(“`r`n”) which will separate it on the line break’s rather than the carriage returns. You can see if we now run the following command $srdm.Split(“`r`n”).count that the number is vastly larger than 1 which was the previous result

So we can now use the simple foreach to step through each line in the variable, which will look like this:

$RDMVerChk = “RDMEnterprise”

$wc = New-Object Net.WebClient

$srdm = $wc.DownloadString(“http://remotedesktopmanager.com/products.htm”)

$out = @()

foreach($rdm in $srdm.Split(“`r`n”).count)

{

$obj = New-Object PSObject

if ($rdm -like “*$RDMVerChk*”)

{

$s = $rdm.Split(“=”)

$obj | Add-Member Noteproperty Name $s[0].split(“.”)[1]

$obj | Add-Member Noteproperty Value $s[1]

$out += $obj

}

}

You will see that I have also added a check only return the strings which contain RDMEnterprise as this is the version of RDM we use, you can change the $RDMVerChk variable to the required version. The next step is to create new PSobject to capture the results into a simple Array for use later in the script.

Now the next step is to check in SCCM to check if the version available from the RDM website is currently available, and if it isn’t to publish it.

To start we need to import the Configuration Manager PowerShell module with the following command:

Import-Module $env:SMS_ADMIN_UI_PATH.Replace(“i386”,“ConfigurationManager.psd1”) -Force

Once imported we need to set the current PowerShell location to the SCCM SIteCode for this example P01

Set-Location “P01:”

Now we can completed a simple Get-CMApplication to see if the RDM Version exists, we will use the expected name of the application we create during running this script which is “Remote_Desktop_Manager-$rdmver where $rdmver is the RDM version from the Website.

$rdmver = $out | ForEach-Object {if ($_.Name -eq “Version”){$_.Value}}

if((Get-CMApplication -Name “Remote_Desktop_Manager-$rdmver))

{

write-output “Already Available”

}

else

{

write-output “Needs to be created”

}

Great now we just need to add the application to SCCM, and make it available to users via the Application Catalog. To do this we use the following script:

#download File

$wc.DownloadFile(($out | ForEach-Object {if ($_.Name -eq “exe”){$_.Value}}), $path\RDM.exe”)

#Create RDM folder and Extract MSI from exe

if(!(Test-Path -path $Path\RDM”)){New-Item -Path $Path\RDM” -Force -ItemType Directory | Out-Null}

Start-Process -FilePath $path\RDM.exe” -ArgumentList “/extract:$path\RDM” -Wait

# Extract .ico file from the RDM.exe

[System.Drawing.Icon]::ExtractAssociatedIcon($path\RDM.exe”).ToBitmap().save($path\RDM\RDM.ico”)

$path = $path\RDM”

$InstallFile = (Get-ChildItem -Path $path | where {$_.name -Like “*.msi”}).name

# Create Folders and move files around

if(!(Test-Path -path $DestPath\applications”)){New-Item -Path $DestPath\applications” -Force -ItemType Directory | Out-Null}

if(!(Test-Path -path $DestPath\applications\$prodman)){New-Item -Path $DestPath\applications\$prodman -Force -ItemType Directory | Out-Null}

if(!(Test-Path -path $DestPath\applications\$prodman\$prodname$rdmver)){New-Item -Path $DestPath\applications\$prodman\$prodname$rdmver -Force -ItemType Directory | Out-Null}

Copy-Item $path\*” $DestPath\applications\$prodman\$prodname$rdmver

# Create SCCM Application

Set-Location $Sitecode`:”

# get existing Deployment Types to be SuperSeeded

$oldappdt = Get-CMDeploymentType -ApplicationName $prodname*”

$newapp = New-CMApplication -Name $prodname$rdmver -Publisher ($prodman.replace(” “,“_”)) -SoftwareVersion $rdmver -IconLocationFile $path\rdm.ico” -LocalizedApplicationName ($prodname.replace(” “,“_”))

Add-CMDeploymentType -MsiInstaller -InstallationFileLocation $DestPath\applications\$prodman\$prodname$rdmver\$installfile -Application $newapp -ForceForUnknownPublisher:$true

# Create SuperSeedence for each existing Deployment Type

foreach ($dt in $oldappdt)

{

# You need to get the new Deployment Type each time as it changes when new SuperSeedences are added.

$appdt = Get-CMDeploymentType -ApplicationName $prodname$rdmver

Add-CMDeploymentTypeSupersedence -SupersedingDeploymentType $dt -IsUninstall $true -SupersededDeploymentType $appdt

}

# Disribution Content to a DP (this can be changed to DP Group as needed)

Start-CMContentDistribution -Application $newapp -DistributionPointName $DP

# Create New User collection for deployment, defineing the limiting collection and the collection to include (Can be changed to Query As required)

$newcol = New-CMUserCollection -Name “Install $ProdMan $Prodname $rdmver -LimitingCollectionName $LimitingCol

Add-CMUserCollectionIncludeMembershipRule -Collection $newcol -IncludeCollectionName $IncCol

# deploy New Application to New Collection

Start-CMApplicationDeployment -CollectionName ($newcol.Name) -Name ($newapp.LocalizedDisplayName)

As you can see it is pretty much broken up into 3 phases, Download & Prepare the file, Confirm if folders exist on Media Store & copy extracted files up to the new folders, And create SCCM Application, Supersede old versions & deploy the new application to a New Collection. I will focus on the first 2 phase’s as they contain the interesting parts, while the last phase is using the default create collection & deployment commands for SCCM.

Download phase, we obtain a copy of the file using this command:

$wc.DownloadFile(($out | ForEach-Object {if ($_.Name -eq “exe”){$_.Value}}), $path\RDM.exe”)

Where $wc & $out still exist from earlier use scripts

The next step is to create a scratch folder to target the exe’s /Extract command at, this will give us the following 2 files

The next step is to create the .ico we can use in the Application Catalog to make it look pretty for the end user

[System.Drawing.Icon]::ExtractAssociatedIcon($path\RDM.exe”).ToBitmap().save($path\RDM\RDM.ico”)

Which we are saving into the scratch folder to copy up to the SCCM server. We also define $InstallFile

From the MSI that exists in the scratch folder rather than using assuming that Devolutions will continue to use the same naming convention for the msi file.

The creation of the folders is pretty self-explanatory.

The next phase is to create the SCCM Applications & supersede the old versions.

We obtain the list of existing Application Deployment Types as the first step to ensure it doesn’t include the application we are creating.

With New-CMApplication in addition to the mandatory parameters we also see -LocalizedApplicationName & -IconLocationFile these are used to define the appearance of the application when published in the Application Catalog, the IconLocationFile parameter have the requirement of either a .ico or image file to work, while LocalizedApplicationName is a free text field.

With Add-CMDeploymentType we have included the following parameter -ForceForUnknownPublisher:$true which ensure that if the MSI file is not signed it will still add the deployment type, You should only use this on MSI’s you know & trust the origin of.

To Supersede the existing versions of Remote Desktop Manager we are using the Add-CMDeploymentTypeSupersedence command, as noted above, every time you add a new Superseded deployment type to the new Deployment type you will need reload the variable as it has changed.

So that’s the breakdown, the Whole script looks like this (Make sure you fix the Quotes when copying from the internet when not using PowerShell 5):

Set-Location “c:”

$Sitecode = “” #SCCM SiteCode

$path = “C:\data”

$Destpath = “” #Path to Central media store eg: \\<servername>\Source$

$dp = “” #FQDN of DP to deploy too (single DP at this point)

$LimitingCol = “” # name of limiting Collection

$IncCol = “” # name of collection to include

$RDMVerChk = “RDMEnterprise” # version of RDM

Import-Module $env:SMS_ADMIN_UI_PATH.Replace(“i386”,“ConfigurationManager.psd1”) -Force | Out-Null

$curdrv = (Get-Location).path

$wc = New-Object Net.WebClient

$srdm = $wc.DownloadString(“http://remotedesktopmanager.com/products.htm”)

$out = @()

foreach($rdm in $srdm.Split(“`r`n”))

{

$obj = New-Object PSObject

if ($rdm -like “*$RDMVerChk*”)

{

$s = $rdm.Split(“=”)

$obj | Add-Member Noteproperty Name $s[0].split(“.”)[1]

$obj | Add-Member Noteproperty Value $s[1]

$out += $obj

}

}

$rdmver = $out | ForEach-Object {if ($_.Name -eq “Version”){$_.Value}}

Set-Location $Sitecode`:”

if((Get-CMApplication -Name “Remote_Desktop_Manager-$rdmver))

{

Set-Location $curdrv

}

else

{

Set-Location $curdrv

#download File

$wc.DownloadFile(($out | ForEach-Object {if ($_.Name -eq “exe”){$_.Value}}), $path\RDM.exe”)

#Create RDM folder and Extract MSI from exe

if(!(Test-Path -path $Path\RDM”)){New-Item -Path $Path\RDM” -Force -ItemType Directory | Out-Null}

Start-Process -FilePath $path\RDM.exe” -ArgumentList “/extract:$path\RDM” -Wait

# Extract .ico file from the RDM.exe

[System.Drawing.Icon]::ExtractAssociatedIcon($path\RDM.exe”).ToBitmap().save($path\RDM\RDM.ico”)

$path = $path\RDM”

$InstallFile = (Get-ChildItem -Path $path | where {$_.name -Like “*.msi”}).name

# Create Folders and move files around

if(!(Test-Path -path $DestPath\applications”)){New-Item -Path $DestPath\applications” -Force -ItemType Directory | Out-Null}

if(!(Test-Path -path $DestPath\applications\$prodman)){New-Item -Path $DestPath\applications\$prodman -Force -ItemType Directory | Out-Null}

if(!(Test-Path -path $DestPath\applications\$prodman\$prodname$rdmver)){New-Item -Path $DestPath\applications\$prodman\$prodname$rdmver -Force -ItemType Directory | Out-Null}

Copy-Item $path\*” $DestPath\applications\$prodman\$prodname$rdmver

# Create SCCM Application

Set-Location $Sitecode`:”

# get existing Deployment Types to be SuperSeeded

$oldappdt = Get-CMDeploymentType -ApplicationName $prodname*”

$newapp = New-CMApplication -Name $prodname$rdmver -Publisher ($prodman.replace(“_”,” “)) -SoftwareVersion $rdmver -IconLocationFile $path\rdm.ico” -LocalizedApplicationName ($prodname.replace(“_”,” “))

Add-CMDeploymentType -MsiInstaller -InstallationFileLocation $DestPath\applications\$prodman\$prodname$rdmver\$installfile -Application $newapp -ForceForUnknownPublisher:$true

# Create SuperSeedence for each existing Deployment Type

foreach ($dt in $oldappdt)

{

# You need to get the new Deployment Type each time as it changes when new SuperSeedences are added.

}

# Disribution Content to a DP (this can be changed to DP Group as needed)

Start-CMContentDistribution -Application $newapp -DistributionPointName $DP

# Create New User collection for deployment, defineing the limiting collection and the collection to include (Can be changed to Query As required)

$newcol = New-CMUserCollection -Name “Install $ProdMan $Prodname $rdmver -LimitingCollectionName $LimitingCol

Add-CMUserCollectionIncludeMembershipRule -Collection $newcol -IncludeCollectionName $IncCol

# deploy New Application to New Collection

Start-CMApplicationDeployment -CollectionName ($newcol.Name) -Name ($newapp.LocalizedDisplayName)

# return to original Path

Set-Location $curdrv

}

Good Luck

Steve

Create a Device Collection based on a User Collection

Create a Device Collection based on a User Collection

I had an interesting discussion with a past colleague the other day where he was asking around to find out if it was possible to create a Device Collection based off a User Collection using the Primary Device option. After a bit of back and forth and him providing the query he was using for the user collection we started playing around with SubSelect queries to see if we can even reference User objects in the device collection.

So to start off we create the simple user query of get user x when in domain y like so:

select *

from SMS_R_User

where SMS_R_User.UserPrincipalName Like ‘%hosking%’

and SMS_R_USER.WindowsNTDomain like ‘%domain%’

Which will return my user account into a User Collection let’s call “Steves User Account” with a collection ID of “P0100024” from here we will move over to the device collection area of the console and create a collection called “Steve Primary Computers” and for this collection we will use the following query:

select *

from SMS_R_System

where SMS_R_System.resourceID in

(select SMS_UserMachineRelationship.resourceID

from SMS_R_User

join SMS_UserMachineRelationship on SMS_UserMachineRelationship.UniqueUserName = SMS_R_User.UniqueUserName

where SMS_R_User.UserPrincipalName Like ‘%hosking%’

and SMS_R_USER.WindowsNTDomain like ‘%domain%’)

So what we are doing here is using the SMS_UserMachineRelationship table which holds the information for Primary Computers for each user, and limiting the search for my username and Domain, which we then return the ResourceID, and finally we then return the SMS_R_system Resources which are in the query into the Device collection.

So this works great, but it does mean that I now have the same query in 2 different locations so if I want to change the query in the User Collection, I will then need to update the query in the Computer Collection.

So we looked at just including the User Collection itself in the query, this information is actually captured in a table accessible via WMI in tables that share this type of naming convention: SMS_CM_RES_COLL_<CollectionID> so from here we wrote this query:

select sms_r_system.name

from SMS_R_System

where SMS_R_System.resourceID in

(select SMS_UserMachineRelationship.resourceID

from SMS_CM_RES_COLL_P0100024

join SMS_UserMachineRelationship on SMS_UserMachineRelationship.UniqueUserName = SMS_CM_RES_COLL_P0100024.SMSID)

This query is even easy then the middle one as all we are doing using the UniqueUserName field from the SMS_UserMachineRelationship table and joining it with the SMSID from the collection table and returning the ResourceID like we did in the previous query.

Good Luck

Steve

Capturing installed Windows Features from Client OS into SCCM without touching the MOF

Capturing installed Windows Features from Client OS into SCCM without touching the MOF

Recently I was asked how to capture the Enabled Windows features from client machines, to help identify the crazy cats who have Hyper-V turned on just so they can run an extra OS or 2 on there Surface Pro 3. So this one was an interesting question in the server OS there is the Win32_ServerFeatures WMI class which is great on servers, but doesn’t exist on client OS’s. All of the information is captured in DSIM right, but how do you turn that into a WMI Class for reporting purposes.

I started off by creating a PowerShell Script with plans to run it in DCM to create the Registry keys which we have traditionally used to capture the information in SCCM, which looks like this:

$f = “”

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

 

Push-Location

Set-Location HKLM:

if ((Test-Path .\software\clientFeatures) -eq $false){New-Item -Path .\software -name clientFeatures | out-null}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

New-ItemProperty .\software\clientFeatures -Name $feat -Value $featset -PropertyType “string” -Force | Out-Null

$feat = “”

$featset = “”

}

}

Pop-Location

So let’s breakdown the script and explain what each of the sections are doing

$feature = dism /online /get-features

This puts the default DISM result into the $feature variable

I know your sitting there like me and wondering how you can turn that into a registry entry.

Well that’s where the next phase of the script comes in and creates a single string

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

Which steps through each of the lines in the result of the DISM command, and finds the lines which have “Feature Names” and “State” then writes into string called $f which we will use later. As we have put the , after the Feature name and the ; after the state, we can then split these in the next phase of the script. Which I will now explain what we are doing there:

Push-Location

Set-Location HKLM:

if ((Test-Path .\software\clientFeatures) -eq $false){New-Item -Path .\software -name clientFeatures | out-null}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

New-ItemProperty .\software\clientFeatures -Name $feat -Value $featset -PropertyType “string” -Force | Out-Null

$feat = “”

$featset = “”

}

}

Pop-Location

At a high level we are connecting to HKEY_LOCAL_MACHINE and created a new Key called ClientFeatures under the Software key, then creating a new REG_SZ for each of the features with the states as the value of the key. At this point I was really happy I had all of the features in the registry in a format I could then create a MOF from, this was short lived as I noticed I had over 100 features in the client OS which would create a heartache when creating the MOF file as it would be around 300 lines of boring code. So I went to myself, self why don’t you just create the WMI class with PowerShell then you don’t have to worry about getting the MOF file right, which I responded to self with that’s a great idea I’m amazed I didn’t think of it myself. Below you will see the resulting script:

if ((Get-WmiObject -Class Win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“key”,[System.Management.CimType]::String, $false)

$newClass.Properties[“key”].Qualifiers.Add(“Key”, $true)

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$newClass.Properties.Add($feat,[System.Management.CimType]::String, $false)

$newClass.Put() | Out-Null

$feat = “”

$featset = “”

}

}

}

if ((Get-WmiObject -Class Win32_ClientFeatures).__path -eq $null){$new = $true} else {$inst = (Get-WmiObject -Class Win32_ClientFeatures).key}

if ($new) {$classinstance = $newClass.CreateInstance()}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ($new)

{

$classinstance.$feat = $featset

$classinstance.put() | Out-Null

}

else

{

$fe = Get-WmiObject -Class Win32_clientFeatures

$fe.$feat = $featset

$fe.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

Let’s dive into the breakdown of the slab of code,

if ((Get-WmiObject -Class Win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

This if statement is checking to see if the WMI Class called Win32_ClientFeatures has any properties assigned to it, if the count is less than 1 then we will go ahead and create the WMI Class and create a Property for each of the Windows Features which is this slab of code:

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“key”,[System.Management.CimType]::String, $false)

$newClass.Properties[“key”].Qualifiers.Add(“Key”, $true)

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$newClass.Properties.Add($feat,[System.Management.CimType]::String, $false)

$newClass.Put() | Out-Null

$feat = “”

$featset = “”

}

}

}

We have also created a key called “key” inventive I know, but it works. So we now have a WMI Class with all of the properties the next step is to create a WMI instance of the state of each of the Windows Features like so:

if ((Get-WmiObject -Class Win32_ClientFeatures).__path -eq $null){$new = $true} else {$inst = (Get-WmiObject -Class Win32_ClientFeatures).key}

if ($new) {$classinstance = $newClass.CreateInstance()}

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ($new)

{

$classinstance.$feat = $featset

$classinstance.put() | Out-Null

}

else

{

$fe = Get-WmiObject -Class Win32_clientFeatures

$fe.$feat = $featset

$fe.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

The if statement is checking to see if the there is an existing instance in the WMI object and selects that instance to update, otherwise we will go ahead and create a new instance. As you can see in the script these are 2 different groups of code.

So I now have a great WMI class which I can query with PowerShell and it returns like so:

We are now sitting there really happy with our new WMI class which captures all of the Windows Features settings for the computer great. The next step is to import the new WMI class into SCCM Hardware Inventory, and this is where it became interesting, as I got this error message:

I started doing some digging into this and found that the Remote Server Administration Tools features all started the same along with the language packs, so I tweaked the script to handle this and is still didn’t correct the issue. At about this point I came to the realisation that as easy as it would be from a reporting point of view I would be next to impossible to maintain as each OS version has different features, and then you need to include the RSAT features, and Language packs, which would result of in hundreds of properties required in WMI for each OS just to capture the information.

To get around this we then need to change the WMI class to have 2 properties called Feature which we will make as the Key for the instances, and value which will contain the state for the Feature. To complete this we run the following script:

$f = “”

$new = $true

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

 

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

The first part is the same as above, but once we get to defining the WMI class it changes a little, now rather than stepping through each of windows features and creating a property for each feature we are now just creating 2 properties called “Feature” and “Value”, only if the WMI class doesn’t exist, as defined in the below script:

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

The next step is to create a new instance for each features, defining the feature name and the state like so:

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

We are also checking to see if the instance already exists for the feature, if it does exist, we will need to update the value, otherwise we create a new class instance.

Now when we import the WMI class into the SCCM console it doesn’t throw an error, and we can see the v_GS_ClientFeatures in SQL Reporting Services as an option to report upon now, so how do I get the list of computers which have Hyper-V Enabled, well you create a SQL Report with the following query:

select rsys.Name0

from v_R_System as rsys join

v_GS_CLIENT_FEATURES as feat on rsys.ResourceID = feat.ResourceID

where Feature0 = ‘Microsoft-Hyper-V’ and

value0 = ‘enabled’

And the WMI Query for a collection looks like this:

select * from SMS_R_System inner join SMS_G_System_CLIENT_FEATURES on SMS_G_System_CLIENT_FEATURES.ResourceId = SMS_R_System.ResourceId where SMS_G_System_CLIENT_FEATURES.Feature = “Microsoft-Hyper-V” and SMS_G_System_CLIENT_FEATURES.value = “Enabled”

The DCM detection PowerShell script looks like this:

$compliance = “Compliant”

$f = “”

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class win32_ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -eq 2)

{

foreach ($r in $f.Split(“;”))

{

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

$cls = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

if ($cls.value -ne $featset) {$compliance = “Non-Compliant”}

$feat = “”

$featset = “”

}

}

}

else

{

$compliance = “Non-Compliant”

}

$compliance

And the DCM remediation PowerShell script looks like this:

$f = “”

$new = $true

$feature = dism /online /get-features

foreach ($svc in $feature)

{

if ($svc -like “*Feature Name *”)

{

$f = $f + $svc.Replace(“Feature Name : “, “”) + “,”

}

elseif ($svc -like “*State : *”)

{

$f = $f + $svc.Replace(“State : “,“”) + “;”

}

}

if ((Get-WmiObject -Class ClientFeatures -ErrorAction SilentlyContinue).__PROPERTY_COUNT -lt 1)

{

$newClass = New-Object System.Management.ManagementClass(“root\cimv2”, [String]::Empty, $null)

$newClass[“__CLASS”] = “Win32_ClientFeatures”

$newClass.Qualifiers.Add(“Static”, $true)

$newClass.Properties.Add(“Feature”,[System.Management.CimType]::String, $false)

$newClass.Properties[“Feature”].Qualifiers.Add(“Key”, $true)

$newClass.Properties.Add(“value”,[System.Management.CimType]::String, $false)

$newClass.Put() | out-null

}

 

foreach ($r in $f.Split(“;”))

{

 

if ($r -ne “”)

{

$feat = $r.Split(“,”)[0].ToString()

$featset = $r.Split(“,”)[1].ToString()

if ((Get-WmiObject -Class Win32_ClientFeatures).feature -eq $feat)

{

$fe = Get-WmiObject -query “select * from win32_clientFeatures where feature = ‘$feat‘”

$fe.value = $featset

$fe.put() | Out-Null

}

else

{

$classinstance = $newClass.CreateInstance()

$classinstance.feature = $feat

$classinstance.value = $featset

$classinstance.put() | Out-Null

}

$feat = “”

$featset = “”

}

}

$compliance = “Compliant”

$compliance

For the DCM you will need to do a detection on a string that equals “Compliant” and remediate if it not.

As an recap of the whole blog, you can see there is a couple of ways to capture the information, depending upon the amount of properties it might make sense to have a single WMI instance in the WMI class, but in other cases it might make sense to have a large number of WMI instances in the WMI class, each have their advantages and disadvantages for the methods. The nice part of this solution is we don’t need to worry about making any changes to the MOF which makes life so much easier.

Good Luck

Steve

The Easy way to Deploy Windows 10 Tech Preview using SCCM 2012 R2

The Easy way to Deploy Windows 10 Tech Preview using SCCM 2012 R2

We have all heard the news of the “Awesome” new features which we will love with Windows 10; for the most part I really do love the awesome new features. For this post I’m going to focus on the “keep everything” upgrade of Windows. Yes you herd me correctly, you can keep everything, which includes all of the tweaks you have made to your applications… how awesome is that! The best thing about the whole solution is that you can empower your staff to complete this deployment using the Application Deployment model from SCCM 2012 which I will detail below:

DISCLAIMER: this is NOT officially supported in production yet so you should really test this process in a non-production environment.

First off we need to get the ISO for Windows 10 from Microsoft which if you don’t already have it you can download it from here or MSDN if you are lucky enough to have a subscription.

We then need to mount the ISO and copy the contents into your source directory.

Once copied we then head into SCCM and browse to Software Library\ Application Management\ Applications and Create Application

Select Manually Specify the application information then Next

Fill out the pertinent General information and select Next

Complete the Application Catalog page using the Icon from setup.exe (not required but makes it look pretty) and select Next

Add a Deployment Type following the below:

Change the type to Script Installer which will automatically change it to Manually Specify the Deployment information then select Next

Fill out the name of the Deployment Type, Select Next

Define the location of your source and point to setup.exe as the installation program (for ease of documenting this I haven’t used an unattended.xml but you can in your environment if you want to reduce the user input, use /? On setup.exe to find the details for it.) Select Next

Now on the Detection Method we need to work out how to see if Windows 10.0.9926 is already installed, and confirm that it was successful after the fact to do that we are going to use a PowerShell Script which looks like this:

if (((Get-WmiObject -Class win32_operatingsystem).version).startswith(“10.0.9926”)) {write-host $true} else {}

 The version number can be changed to whatever the target version of windows is.

Select Ok

Then Next

Update the User Experience tab to look like this (making sure to extend the Max allowed
run time), then select Next

For this demo I’m going to leave the Requirements and Dependencies
empty (you might want to limit the version of windows it can target and alike), so Next, Next

Select Next on the Summary Page

Then select Close which will return you to the Create Application Wizard

Which you can select Next, Next, close to complete the creation of the application.

To deploy the application to your test users, select the Application, then Deploy from the Ribbon

As this is a test environment I’m going to use the All Users Collection (in production this should rarely happen) and then select Next

As we have just created the Application the content hasn’t been deployed, so add a Distribution Point, and select Next

Leave the Defaults for the rest of the Wizard, select Next until the end and then Close

You can now browse to your Application Catalog and select the Application we have just deployed from the Catalog.

Which will then appear in Software Center on the client, once the files download the user will be prompted to complete the standard Windows 10 upgrade prompts.

Wait an hour or depending upon how powerful your machine is, then the computer will be running the latest version of Windows 10.

Good Luck

Steve

What is the SMS Provider and Why Should I Care?

What is the SMS Provider and Why Should I Care?

The SMS Provider is the name given to the WMI Provider which SCCM leverages off to access the information from SCCM. The most common usage is to power the SCCM Console, Resource Manager, and most Console Extensions. It can be used to read and write data into the SCCM environment, and any use who has access to the SCCM Console will have access to the Provider. Traditionally the SMS Provider has been the sphere of the guys who do the crazy awesome extensions for the SCCM environment which we all leverage off to make our jobs easier. So why would we want to access the SMS Provider outside of the SCCM Console? That’s a great question. Let’s just say, for example, you want to get the serial number of a computer from SCCM. Via the SCCM Console you would need to find the Computer in the All Systems collection, then right click and select ‘Start the Resource Explorer’, browse to the PC BIOS Folder and then retrieve the Serial Number. Alternatively, using PowerShell, you can connect to the SMS Provider and can run the following script;

$servername = Get-WmiObject -Namespace root\ccm -Query “select * from SMS_authority” | select CurrentManagementPoint

$Providerdetails = Get-WmiObject -ComputerName $servername.CurrentManagementPoint -Namespace root\sms -Class SMS_Providerlocation | select SiteCode, Machine

$sitecode = $Providerdetails.sitecode

$ProviderServer = $Providerdetails.machine

$compname = $env:COMPUTERNAME

$comps = Get-WmiObject -ComputerName $ProviderServer -Namespace root\SMS\site_$sitecode -query “select * from SMS_R_System full join SMS_G_System_PC_BIOS on SMS_R_System.resourceID = SMS_G_System_PC_BIOS.resourceID where SMS_R_System.name = ‘$compname‘”

$comps.SMS_G_System_PC_BIOS.SerialNumber

Yes, I know, scary looking, right? Let’s break down what the script is actually doing;

$servername = Get-WmiObject -Namespace root\ccm -Query “select * from SMS_authority” | select CurrentManagementPoint So this section is getting the current Management Point of the Computer you are running the script from WMI (so long as the SCCM Client is installed).

$Providerdetails = Get-WmiObject -ComputerName $servername.CurrentManagementPoint -Namespace root\sms -Class SMS_Providerlocation | select SiteCode, Machine

This line returns the details for the SMS Provider from the Management Site Server for the SCCM environment which we are connected to via WMI on the Management Point from where we create Variables for the SiteCode and the name of machine with the SMS Provider is installed (if you have multiple SMS Providers/ Management Points you will have multiple results here) and for the example I have defined the variable $compname as the name of the computer running the script, but this can be changed to the machine you want to find the serial number of.

$sitecode = $Providerdetails.sitecode

$ProviderServer = $Providerdetails.machine

$compname = $env:COMPUTERNAME

The next line is where we actually call the SMS Provider, so we connect to the $Provider Server in the root\sms\site_$sitecode namespace, to execute the query to return all of the System and PC BIOS details for the current computer into the $comps Variable which we then interrogate on the next line to return the Serial Number from the SMS_G_System_PC_BIOS object.

$comps = Get-WmiObject -ComputerName $ProviderServer -Namespace root\SMS\site_$sitecode -query “select * from SMS_R_System full join SMS_G_System_PC_BIOS on SMS_R_System.resourceID = SMS_G_System_PC_BIOS.resourceID where SMS_R_System.name = ‘$compname‘”

$comps.SMS_G_System_PC_BIOS.SerialNumber

So that’s great we now have the serial number a lot quicker than finding it via the Console, but I actually want to get the last logged on user that SCCM has for that machine. Well, all we need to do is change the last line; $comps.SMS_R_System.LastLogonUserName

So we have captured the last logged on user from SCCM, and the Serial number, but what about the Operating System Version? Well we just need to change and re-run the query for comps to something like this; $comps = Get-WmiObject -ComputerName $ProviderServer -Namespace root\SMS\site_$sitecode -query “select * from SMS_R_System full join SMS_G_System_Operating_system on SMS_R_System.resourceID = SMS_G_System_Operating_system.resourceID where SMS_R_System.name = ‘$compname‘” $comps.SMS_G_System_Operating_system.caption 

You will see the only thing I changed between the queries is what SMS_R_System is joined to, to return the caption (where the OS Name is contained if you didn’t know) from the Operating System Table we select the sms_g_system_operating_system.caption object from inside the $comps variable. You can also just type $comps.sms_g_operating_system and it will return all of the Properties for that WMI Class if you don’t know which Property you need. So by now you might be going, “That’s great Steve, you know what variables and tables to be looking for but how the heck am I meant to figure that out?” Well, Kaido has created this awesome Excel Sheet which details all of the Classes for WMI, both Server and Client side which you can download from here and look at the Server WMI Class Properties tab. This will detail all of the default Classes and Properties, if you have extended your hardware inventory to capture even more information it’s really easy to interrogate that information as well, as the Class will appear as SMS_G_System_ and the Properties will match what you added to the Hardware Inventory. To summarize while learning how to use the SMS Provider you might be a little longer to get the information, but in the long term it will save a whole heap of time! Good Luck Steve

Actual Installed Application Reporting from SCCM

Actual Installed Application Reporting from SCCM

Having worked on SCCM for many years now I have lost count of how many times I have been asked, “Since you guys have an agent on all of the computers can I get you to provide me a list of ALL software that is installed on all of the computers?” after which there is a long draw out discussion explaining that when SCCM returns the information from Programs and Features it doesn’t discriminate between System install applications which are not visible in Programs and Features in which you will typically end up with double the amount of applications per computer, then what actually appears in Programs and Features. To this end I’ve had some spare time recently, so I have done some investigating, and found that with a simple update of the MOF we can typically get within +/-98% accuracy of only the applications that appear in Programs and Features appearing in the SCCM reports, which for me it a huge win.

As we know the Registry is the master of what appears in the Programs and Features list, so for example you can change the Display Name of applications in Programs and Features by changing the following registry Item:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\|DisplayName

This Key also includes such things as the Publisher, Version numbers, and Uninstallation strings. The Item’s which we are interested in for this solution are the following:
SystemComponent
ParentDisplayName

The SystemComponent item will appear as 1 if the application is to be hidden from Programs and Features, so for example the SCCM Client will have SystemComponent = 1 which appears to hide it from Programs and Features, if this item is not defined it is assumed to be a 0 (This is also used by Microsoft Office to hide each of the components from Programs and Features).

Whereas the ParentDisplayName item is used for patches to applications and Operating system, an example of can be seen in Windows XP where you could select the check box to hide updates. When not referring to the install Operating System it will refer to the DisplayName of the parent, for example “Microsoft .NET Framework 4 Client Profile” will have all of the updates that are dependent upon it.

Now we have explained what we are doing and why let’s step through the process of actually capturing the details in SCCM.

  1. Browse to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
  2. Copy Configuration.mof to c:\data\mofs
  3. Open Configuration.mof with Notepad
  4. Find the Follow:
    class Win32Reg_AddRemovePrograms
    {
    [key]
    string ProdID;
    [PropertyContext(“DisplayName”)]
    string DisplayName;
    [PropertyContext(“InstallDate”)]
    string InstallDate;
    [PropertyContext(“Publisher”) ]
    string Publisher;
    [PropertyContext(“DisplayVersion”)]
    string Version;
    }
    and Replace with the following:
    class Win32Reg_AddRemovePrograms
    {
    [key]
    string ProdID;
    [PropertyContext(“DisplayName”)]
    string DisplayName;
    [PropertyContext(“InstallDate”)]
    string InstallDate;
    [PropertyContext(“Publisher”) ]
    string Publisher;
    [PropertyContext(“DisplayVersion”)]
    string Version;
    [PropertyContext(“SystemComponent”)]
    string SystemComponent;
    [PropertyContext(“ParentDisplayName”)]
    string ParentDisplayName;
    }

  5. Then find the following directly below:
    class Win32Reg_AddRemovePrograms64
    {
    [key]
    string ProdID;
    [PropertyContext(“DisplayName”)]
    string DisplayName;
    [PropertyContext(“InstallDate”)]
    string InstallDate;
    [PropertyContext(“Publisher”) ]
    string Publisher;
    [PropertyContext(“DisplayVersion”)]
    string Version;
    }
    and Replace with the following:
    class Win32Reg_AddRemovePrograms64
    {
    [key]
    string ProdID;
    [PropertyContext(“DisplayName”)]
    string DisplayName;
    [PropertyContext(“InstallDate”)]
    string InstallDate;
    [PropertyContext(“Publisher”) ]
    string Publisher;
    [PropertyContext(“DisplayVersion”)]
    string Version;
    [PropertyContext(“SystemComponent”)]
    string SystemComponent;
    [PropertyContext(“ParentDisplayName”)]
    string ParentDisplayName;
    }

  6. Close and Save Configuration.mof
  7. In an elevated command prompt run the following commands:
    1. Mofcomp c:\data\configuration.mof
      the result should look like this:
  8. Browse back to: \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv and rename the existing file to match this naming convention:
    1. configurationYYYYMMDD.mof
  9. Copy configuration.mof from c:\data to \\<SCCMSERVER>\SMS_<SITECODE>\inboxes\clifiles.src\hinv
  10. Open SCCM 2012 Console and browse Administration à Client Settings
  11. Right click on Default Client Settings and select Properties
  12. Select Set Classes under the Hardware Inventory tab on the Default Settings Form
  13. Select Export on the Hardware Inventory Classes form
  14. Save the exported file to c:\data\export.mof
  15. Open C:\data\export.mof in notepad
  16. Find the following:
    class Win32Reg_AddRemovePrograms : SMS_Class_Template
    {
    [SMS_Report (TRUE), key ]
    string ProdID;
    [SMS_Report (TRUE) ]
    string DisplayName;
    [SMS_Report (TRUE) ]
    string InstallDate;
    [SMS_Report (TRUE) ]
    string Publisher;
    [SMS_Report (TRUE) ]
    string Version;
    };

    and replace with:
    class Win32Reg_AddRemovePrograms : SMS_Class_Template
    {
    [SMS_Report (TRUE), key ]
    string ProdID;
    [SMS_Report (TRUE) ]
    string DisplayName;
    [SMS_Report (TRUE) ]
    string InstallDate;
    [SMS_Report (TRUE) ]
    string Publisher;
    [SMS_Report (TRUE) ]
    string Version;
    [SMS_Report (TRUE) ]
    string SystemComponent;
    [SMS_Report (TRUE) ]
    string ParentDisplayName;
    };

  17. Then find the following directly below:
    class Win32Reg_AddRemovePrograms64 : SMS_Class_Template
    {
    [SMS_Report (TRUE), key ]
    string ProdID;
    [SMS_Report (TRUE) ]
    string DisplayName;
    [SMS_Report (TRUE) ]
    string InstallDate;
    [SMS_Report (TRUE) ]
    string Publisher;
    [SMS_Report (TRUE) ]
    string Version;
    };

    and replace with:
    class Win32Reg_AddRemovePrograms64 : SMS_Class_Template
    {
    [SMS_Report (TRUE), key ]
    string ProdID;
    [SMS_Report (TRUE) ]
    string DisplayName;
    [SMS_Report (TRUE) ]
    string InstallDate;
    [SMS_Report (TRUE) ]
    string Publisher;
    [SMS_Report (TRUE) ]
    string Version;
    [SMS_Report (TRUE) ]
    string SystemComponent;
    [SMS_Report (TRUE) ]
    string ParentDisplayName;
    };
  18. Close and Save c:\data\export.mof
  19. On the Hardware Inventory Classes form select Import
  20. Review the Message to ensure everything is correct, and Select Import.
  21. On the Hardware Inventory Classes form search for Win32reg_addremoveprograms and ensure that SystemComponent & ParentDisplayName are selected, then click on OK, and OK, back to the SCCM Console

To test ensure that you have completed a Policy refresh on a targeted computer, wait a few minutes then trigger a hardware inventory, you can see if the policy has updated by reviewing the InventoryAgent.log on the client computer and filter with CMTrace for Win32reg_addremoveprograms, you should see something like this:

And on the server side you can confirm the new inventory has been loaded by reviewing the DataLdr.log file on the Site server, looking for the line
“Done: Machine=(GUID:) code=0 (1473 stored procs in .MIF)”
to ensure that the data has been loaded into the SQL database.

From here we can create a report using the following example SQL query:
Count of all applications installed on all computers:

SELECT
*
FROM
(

SELECT
DISTINCT

v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0 AS “Publisher”,
count(*)
as
‘Install count’

FROM v_GS_ADD_REMOVE_PROGRAMS_64

INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID

JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID

WHERE v_GS_ADD_REMOVE_PROGRAMS_64.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS_64.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS_64.Displayname0 is
null

group
by v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0

UNION ALL

(


SELECT
DISTINCT

v_GS_ADD_REMOVE_PROGRAMS.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS.Publisher0 AS “Publisher”,count(*)
as
‘Install count’


FROM v_GS_ADD_REMOVE_PROGRAMS


INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS.ResourceID


JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID


WHERE v_GS_ADD_REMOVE_PROGRAMS.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS.Displayname0 is
null

    group
by v_GS_ADD_REMOVE_PROGRAMS.DisplayName0, v_GS_ADD_REMOVE_PROGRAMS.Publisher0

))
AS u

ORDER
BY “Product Name”, Publisher

Installed applications on a Single Computer:

DECLARE @compname VARCHAR(MAX) = ‘Computer Name’

SELECT
*
FROM
(

SELECT
DISTINCT

v_GS_ADD_REMOVE_PROGRAMS_64.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS_64.Publisher0 AS “Publisher”, v_GS_ADD_REMOVE_PROGRAMS_64.Version0 AS “Version”

FROM v_GS_ADD_REMOVE_PROGRAMS_64

INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID

JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS_64.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID

WHERE v_GS_ADD_REMOVE_PROGRAMS_64.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS_64.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS_64.Displayname0 is
null

and v_R_System_Valid.Netbios_Name0 = @compname

UNION ALL

(


SELECT
DISTINCT

v_GS_ADD_REMOVE_PROGRAMS.DisplayName0 AS “Product Name”, v_GS_ADD_REMOVE_PROGRAMS.Publisher0 AS “Publisher”, v_GS_ADD_REMOVE_PROGRAMS_64.Version0 AS “Version”


FROM v_GS_ADD_REMOVE_PROGRAMS


INNER
JOIN v_R_System_Valid ON v_R_System_Valid.ResourceID = v_GS_ADD_REMOVE_PROGRAMS.ResourceID


JOIN v_GS_OPERATING_SYSTEM ON v_GS_ADD_REMOVE_PROGRAMS.ResourceID = v_GS_OPERATING_SYSTEM.ResourceID


WHERE v_GS_ADD_REMOVE_PROGRAMS.SystemComponent0 is
null
and v_GS_ADD_REMOVE_PROGRAMS.parentdisplayname0 is
null
and
not v_GS_ADD_REMOVE_PROGRAMS.Displayname0 is
null

and v_R_System_Valid.Netbios_Name0 = @compname

))
AS u

ORDER
BY “Product Name”, Publisher

You will notice that we are not using the “v_add_remove_programs” view, this appears to be a derived view which doesn’t appear to be updated when new columns added to the Class, but we can get around this by using the Union function in SQL.

Good Luck

Steve

Logical steps to automate a task

Logical steps to automate a task

In this blog I’m going to detail the logic steps that I use to automate a task, I can’t say this is the best way or the most efficient way to complete the task but it is the process that I have developed over the last decade for myself to automate tasks.

First off we start with the what, when, why, and how of the task so in this example let’s detail the clean-up of a folder on a group of computers let’s call it:

c:\users\<username>\appdata\roaming\Apple computer (you know the folder that roams with the backup of your iPhone)

So we have the What which is empty the apple computer folder

The When well you would want to complete this task before the roaming profiles starts to copy the files up to your file server.

The Why is a simple one where we want to stop the replication of a stupid amount of data when you log on and off the computer.

And the How in this circumstance we are going to use an SCCM compliance settings.

These all group together to provide us a purpose as to why we are making this change. The next step is to start stepping out the process, I personally prefer to do this with a pencil and paper with a rubber, as it allows you to remove the computer element from your planning. In saying that Visio does the task just as well if not better as you don’t have to keep rubbing things out if you keep missing steps. My process flowcharts look something like this:

remediate

So we have a simple process mapped out now which gives us a high level break down of the steps required to complete the task.

From here I like to put fragments of code that I’m going to use next to each process step, this give me the ability to capture where I use the same process repeatedly where it might suit a sub routine or function.

In this example I will use VBscript as it might need to run on machines that don’t have PowerShell I’m looking at you XP.

remediatewithscript

So from here we can create a script that looks something like this:

 


strComputer = "."
dim OS
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
OS = objOperatingSystem.Caption
Next
 
if instr(os, "XP") or instr(os, "2003") then
                userpath = "c:\documents and settings"
else
                userpath = "c:\users"
end if
 
set FSO = createobject("Scripting.FileSystemObject")
userfolder = fso.getfolder(userpath)
 
for each subfolder in userfolder
                if FSO.folderexists(subfolder.path & "\appdata\roaming\Apple computer") then
                                FSO.deletefolder(subfolder.path & "\appdata\roaming\Apple computer")
                end if
next

 

This will remediate the issue on all of the targeted computers. Now we need to make it so we can detect if the folder actually exists on the computer to use the SCCM Compliance Settings.

The process is very much the same for the Detection method but rather than deleting the folder if we find it we will return back that we have found the folder and it would look something like this:

Discovery

Obviously we can use the same process to create the script to detect the folder, and reuse a good part of our previous script, on the process diagram it would look like this:

Discoverywithscript

Which results in a script that looks like this:

 


strComputer = "."
dim OS
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
OS = objOperatingSystem.Caption
Next
 
if instr(os, "XP") or instr(os, "2003") then
                userpath = "c:\documents and settings"
else
                userpath = "c:\users"
end if
 
set FSO = createobject("Scripting.FileSystemObject")
userfolder = fso.getfolder(userpath)
 
noncompliant = 0
for each subfolder in userfolder
                if FSO.folderexists(subfolder.path & "\appdata\roaming\Apple computer") then
                                noncompliant = noncompliant + 1
                end if
next
 
if noncompliant > 0 then
                wscript.echo "Non-Compliant"
else
                wscript.echo "Compliant"
end if

From here we can create a script based configuration item and apply it to a configuration baseline in SCCM with remediation.

We now have everything required to create our new Configuration Item in SCCM 2012 to create this we need to open the SCCM 2012 Console and navigate to Assets and Compliance, Compliance Settings, Configuration Items.

From here we can select the Create Configuration Item task from the Ribbon

createconfigitem

On the Create Configuration Item Wizard form enter a descriptive name something like U – Cleanup Apple Backups which provides a quick view of what the task is, I would also strongly encourage the use of the Description box to save you needing to try to pull apart your Configuration Item 6 months down the track when you have to fix it.

On the next page we can define the Platform we are wanting to support, as we handle this in the script we can just leave it with the default of select all.

Now we can create a new Settings object and fill it out to look something like this:

createsetting

You will see the Red error mark next to the Add Script for Discovery Script, let’s go ahead and add the script we create earlier making sure to change the script language to vbscript

discoscript

Complete the same steps to add the Remediation script, and select OK to close out of the Create Setting form.

On the Compliance Rules stop in the Create Configuration Item Wizard we are now going to define on what condition we will declare noncompliance needing remediation, which looks something like this:

compliacerule

Note the field for “the following values” is where we handle the command we echo in the script.

From here we can complete the wizard and apply the new Configuration Item to a baseline and your systems and clean up the Apple backup folder before it clogs up your roaming profiles.

I hope this has provided an insight in to a process that you can use to create automated processes.

Good Luck

Steve

Why could SCCM Compliance Settings replace Group Policy?

Why could SCCM Compliance Settings replace Group Policy?

My hope with this post is to plant an idea in your mind on what could be done with SCCM Compliance Settings over using Group Policy. Let’s start off with an over view of the 2 options we will be discussing today.

Group Policy (GPO)

You can trace Group Policy all the way back to the first version of Active Directory in Windows Server 2000, the use of ADM files can be traced back even further into NT4, and windows 95/98 using the System Policy Editor which was part of the NT 4.0 Resource Kit which you can download here for those of you still using NT as your domain. Don’t get me wrong along the way Microsoft has added more features into Group Policy, for example Group Policy Preferences which was part of the acquisitions of Desktop Standard in 2006 originally called PolicyMaker. This alone has made group policy quite a powerful tool since Server 2008, the centralisation of the task like Printer and Drive Mapping which previously the easiest way to deploy these settings was Logon Script. The addition of the Advance Group Policy Management tool in the MDOP was great for those large companies which had SA and then also know about it, and admins didn’t know a password for a domain admin account, but a step in a very good direction none the less. Let’s face it most vendors that are doing large-scale deployments provide ADM/x files to configure the application as it still is a very easy way to empower the admins.

One of my biggest gripes with Group Policy was very noticeable in Windows XP, and less so in the new Windows Versions is that it is a set and hope solution, what I mean by this is that if I define a policy to my whole fleet without completing a process which is not out of the box I have to hope that the settings have applied, and typically I find out a month later when somebody raises the issue that was meant to be resolved.

Compliance Settings

Much like Group Policy you can trace Compliance Settings back a few generations of SCCM/SMS, to SMS 2003 to be precise. I have to on good authority it was originally created to confirm that exchanges servers in a large company had the same settings as each other. In 2003 the feature was called Desired Configuration Monitoring it was very much a passive process of collecting information, from which you could pull a report from. In SCCM 2007 this feature was renamed to Desired Configuration Management, and improved slightly it was included as a component that ran under ccmexec, it was converted to XML files stored on the client computer rather than WMI Classes which were returned with Hardware Inventory. Again it was very much a passive reporting feature which required manual steps to remediate the problems, and in large environments it gave you some where to start. Then Microsoft moved to SCCM 2012 and like a good deal of the product this component was given a comprehensive overhaul, along with the Compliance Settings moniker. As part of the overhaul a few new options were added, such as Remediation, a larger range of detection types (AD, Registry Keys, Scripts, SQL/WQL queries to name a few), and the ability to create a Collection from each deployment dependent on the status message.

Until SCCM 2012 I didn’t see a huge use for DCM as I was managing sites of around the 5000 seat mark, which is just on the cusp of needing to have everything repeatable. But with SCCM 2012 I can see great potential for Compliance Settings to replace large parts of Group Policy Objects which will not only allow for the reporting of settings, but ideally reduce the logon/startup time for our computers. In addition to this it has the ability for staff to run the evaluation independent of the schedule & create a local report without using the commandline. One of the downsides is this is equivalent to a local group policy setting, which is trumped by the Domain Group Policy, so it is one or the other for each of the defined settings.

Demo

Group Policy

So let’s for example say you work for a company which wants to annoy your staff by defining a standard wallpaper for all staff.

So in Group Policy we would create a new Group Policy Object, and define the following setting:

To point to in this example c:\windows\demo.jpg and defined to be centered.

We then assign it to the desired OU and it’s deployed.

Then you get the staff that have managed to convince the people of power, that to complete their work they must have local admin access to the work computer, you know the type that install “freeware” software for Business purposes and deny it when you approach them for a licence key. These guys are super smart and work out that they can change the wallpaper by making a regkey change, or replacing a file. As stated above there is no way out of the box to report on this, so you get questions from your managers as to why these guys have different wallpapers and the inevitable how can I change theirs for them.

dcm1

SCCM Compliance Settings

So with SCCM to define this we first need to get the RegKey that is being defined as part of the Group Policy Setting, for something like Desktop Wallpaper you can google Bing it and some bright spark will have published it, like this: HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System Wallpaper – REG_SZ WallpaperStyle – REG_SZ

For the more complicated settings if they are default Windows polices you can download excel files which detail each policy and the regkey that is set from here and for Office products you can find the link here.

We now have the registry key for our new Configuration Item in SCCM 2012 to create this we need to open the SCCM 2012 Console and navigate to Assets and Compliance, Compliance Settings, Configuration Items.

From here we can select the Create Configuration Item task from the Ribbon

dcm2

On the Create Configuration Item Wizard form enter a descriptive name something like

U – Desktop Wallpaper which represents that we are defining a user setting for the desktop wallpaper, in addition to this I would recommend put a brief blurb in the Description box as you will be wondering why you created the item 6 months later.

The other part you need to make sure is that if you are defining a registry key that the settings applies to the operating systems which you are targeting on the Supported Platform page.

When we get to the point of creating a new Setting this is where we can define multiple settings that must be met, for example a scripted item to confirm the wallpaper file hasn’t been changed, and a registry value to ensure that the registry key hasn’t been changed.

Something like this for the registry key item:

dcm3

We can then create compliance rules around the setting, which is where we can set it to report on if a certain value exists, and that it meets a defined task. In this example we want Wallpaper to equal “C:\windows\demo.jpg” in addition to reporting on if the systems are compliant, we can also remediate the setting where desired, in this case we do want to remediate the setting as shown below:

dcm4

We have now created the Compliance Item which we can deploy to our users/computers with a configuration baseline. Again use a descriptive name, so for this demo we will use U – Desktop Wallpaper and add the Configuration Item that we created earlier.

dcm5

We then deploy this to our desired collections, and this is where we can create multiple deployments one which will remediate the setting, and the other to only report on it, along with how often we want the evaluation to run on the computers.

dcm6

On the client side your users will see the following tab in the Configuration Manager Client in the control panel.

dcm7

As you can see you have ability to evaluate each of the Assigned baselines, along with providing a local report of the compliance of the baseline.

Good Luck

Steve

Reporting on Shares with SCCM 2012

Reporting on Shares with SCCM 2012

To follow in the same vain as my last post for reporting on usage of logon scripts, we now need to find which shares have been removed since the last time the logon script was updated. To do this is reasonably easy to complete by connecting to each share and checking to see if they still exist, until you have a list of over 200 shares to check. So let’s take a look at what we can do with SCCM to handle this.

First task would be to identify if we can utilise an existing WMI class to query, thankfully there is the Win32_Share class which will return the information back to SCCM. In addition to this we can enable this class via selecting it in the Hardware Inventory for the client settings.

RSpic1

Once this has been enabled and some of the computers have reported back you can use the below query to your database. To present the data back in a usable form I have joined the share path.

RSpic2

 

Now we have the server names tied to the share paths, from this we can grab the shares from the logon script and do a query like this

 

select ‘\\’ + rsys.name0 + ‘\’ + sha.name0
from v_GS_SHARE as sha
Join v_r_system as rsys on sha.resourceID = rsys.resourceID
where ‘\\’ + rsys.name0 + ‘\’ + sha.name0 in (‘\\cm7\admin4’,
‘\\or7\c$’)

This will bring back a nice list we can then copy into excel and do a simple countif on the 2 columns of share paths.

The problem we have is when it comes to Clustered shares, as since Server 2000 Microsoft hasn’t represented the shares for Clusters with the Win32_Share class. But from Server 2008 R2 we have be able to query the Win32_Clustershare. The process to add this class to the hardware inventory in SCCM is a little bit different as you need to add the class to the list,

To do this open the “default client settings” policy and browse the hardware inventory, then select Classes to inventory. once this appears select Add and then on the screen that appears hit the connect button. Which will bring a prompt like this. (Yes Microsoft forgot to select the password field as hidden). Make sure you select Recursive otherwise most classes wont appear.

RSpic3

Once you select connect, search the win32_clustershare class and enable it.

RSpic4

Then we can select the Fields that we want to inventory either in the Default policy to apply to all computers in the environment, or apply to a new/existing client settings policy.

RSpic5

Once we have deployed the Client Settings to the cluster we can return the data with the below query, as you can see the default fields are structured a little bit differently to the win32_share class. The class has both the server name and the Share Path which saves creating a constructed string.

RSpic6

Much like the Win32_Share class we can do a where in query which would look something like this:

select ServerName0,
Path0,
Name0
from v_GS_CLUSTER_SHARE
where name0 in (‘\\svr354\ab’,
‘\\svr354\acc’)

From here you have a couple of options on how to move forward, for example you can create both of these queries as data sources in an Excel Workbook with the 3rd sheet containing a list of all your shares from your logon script, from which you can derive the exists of each of the shares from either the win32_share or the Win32_clustershare data sources.

Cheers

Steve

UPDATED: missing image.

Reporting on logon scripts with SCCM 2012

Reporting on logon scripts with SCCM 2012

I had a use case to be able to report on the Current logon scripts in our environment, along with the status of the user accounts. This report is to be used to plan the consolidate all of our many logon scripts into a single script.

So by default we all look to running a powershell script or alike to return the logon script details from Active Directory and use that to plan the consolidation, as we all know this is a static list which we need to obtain on a regular bases to ensure that we keep track of the changes.

So I had the idea of what if we can capture this information in a SQL database and make it work for us, thankfully Microsoft has a really easy tool to handle this out of the box, it’s called System Center Configuration Manager (both 2007 & 2012 Supports this method.).

To enable the inventory of addition Active Directory fields into the SCCM database it’s a simple as updating the Active Directory User Discovery method to include the scriptpath attribute.

1

This is accessible on the last tab of the “Active Directory User Discovery Properties” (2012 has the nice search function to ensure correct spelling 🙂 )

2

Run a full discovery on the Active Directory Users. Once the scan has completed you can check in the adusrdis.log file to confirm the agent has completed, we can now run the SQL query of (make sure you change the domain name to match your domain), go ahead I’ll wait:

SELECT user_name0,

user_Account_control0,

CASE user_Account_control0

WHEN ‘512’ THEN ‘Active Account’

WHEN ‘514’ THEN ‘Account Disabled’

WHEN ‘66048’ THEN ‘Password Does Not Expire’

WHEN ‘544’ THEN ‘does not require Password’

WHEN ‘590336’ THEN ‘Trusted for Delegation and Password does not expire’

WHEN ‘66050’ THEN ‘account disabled and does not require password’

WHEN ‘66080’ THEN’Password does not expire and password not required’

ELSE CAST(user_account_control0 as VARCHAR(20)) end as ‘Account Status’,  scriptPath0

FROM v_r_user

WHERE Windows_NT_Domain0=’domain7′

AND NOT user_account_control0 in (

‘66176’/* don’t expire password, emailed, encrypted text password allowed*/,

‘546’/* disabled, Password not required*/,

‘2080’/* Interdomain trust account, Password not required*/,

‘4260352’/* don’t require preauth, don’t expired password, enabled */)

ORDER BY [Account Status]

 Which returns something like this:

3

So we can capture a good amount of information by querying the database directly. The next step is to present it in a fashion that is useful for other staff, so let’s step through the process of putting this into a report.

Browse to your reporting site and if you don’t already have a folder for internal reports create one (keeps it neat and tidy) browse to the folder and select report builder. Once report builder opens select new report, table or matrix

4

And we are wanting to create a dataset

5

Browse for the system generate Data Source Connector, this is located under the configmgr_ folder on the SSRS server.

6

Enter your username & password that has access to the SQL database.

7

Select the edit as text option and paste the above query

8

We can now select how we present the information, if we want to put in a simple table it is a matter of adding all of the options into the values box like this:

9

Which will return a result something like below, which will give us a decent amount of information:

10

But we want to create a report that will allow for a glance to provide a count so we can select the options like this:

11

Which presents a result like this:

12

Which give us a place to start on how to clean up the logon scripts.

One thing to note is with SCCM 2012 the Active Directory User Discovery ignores disabled user objects, whereas SCCM 2007 would bring this information into the database to allow you to report on the information.

Obviously you can replace the scriptpath with almost any active directory user attribute to bring into the SCCM database, the common ones that I bring through include the phone numbers, address fields, title, and manager.

I hope this helps.

Good luck

Steve