This blog post deals with adding fake computers or users to a SCCM environment.

Why would you want to add fake clients to a SCCM environment?
You probably want to test something, maybe load test something, there can be multiple reasons.
But since you are here, you probably have a reason, so let's move on 🙂

I use the CM Client API to create a resource and I say resource because it can also be a user in addition to a computer.

So, let's start with the prerequisites so you don't waste your time here.

  • Configuration Manager Client Messaging SDK
    From here, you actually only need the file 'Microsoft.ConfigurationManagement.Messaging.dll'
  • Configuration Manager PowerShell module
    (Not required when using CmDeviceSeed.exe)
  • You need to be a full admin on SCCM - you know what that is.
  • We will create a certificate that we will have to import into SCCM.
  • CmDeviceSeed.exe requires at least .NET Framework 4.5.

First of all, we need to add a SCCM ISV Proxy Certificate.
That is a selfsigned certificate that we need to import into SCCM in order to create multiple resources.
If we don't have that certificate, each time we add a resource, it will just overwrite the existing. Not much load testing it that, huh?

I always run my device seeder on the SCCM server, but guess you can do that from any machine.
Anyway, create the certificate on the machine you want to run the seeder scripts from.
Run the following PowerShell command to create a self-signed certificate:

New-SelfSignedCertificate -Type Custom -Subject "CN=CmDeviceSeed ISV Proxy Certificate" -KeyLength 2048 -KeySpec KeyExchange -KeyExportPolicy Exportable -FriendlyName "CmDeviceSeed ISV Proxy Certificate" -CertStoreLocation "cert:\LocalMachine\My"

This will place the certificate in the Machine part of the certicate store.

So open the certificate snap-in (Manager Computer Certicates), expand Personal and find the certificate we just generated.
We need to export that to a .cer file:

Export without exporting the private key:

Select to export to a .cer file. Here, I selected the DER encoding, but Base-64 would probably work too:

So, let's finalize this:

Now we need to import that certificate into SCCM.
Open the Console and select the Administration workspace, navigate to Security -> Certificates.

From here, select Register or Renew ISV Proxy, select Register and locate the .cer file we just exported:

You should see that in the list as the type "ISV Proxy"

Did that work? If it did, we are so ready to create some devices!

Create basic computers

We want to see if we can add more than one computer in SCCM, so try that with a little help from the SCCM PowerShell Module.
Also, make sure you know where you put the file 'Microsoft.ConfigurationManagement.Messaging.dll' cause we need that now.

Do you know the site code of your ConfigMgr environment?
I hope you do or you shouldn't be doing this 🙂
Anyway, nobody is perfect, so if in doubt, find it under the Site Configuration -> Sites:

Got it? Great. Use this for the following PowerShell script along with the paths to the PowerShell Module and the Client Messaging SDK dll file:

 $siteCode = "CT1"
 $clientCount = 10
 $clientStartNumber = 1
 $clientPrefix = "WS"

 $modulePath = 'F:\CMDeviceSeed\Microsoft.ConfigurationManagement.Messaging.dll'
 $adminconsolePath = 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
 Import-Module –Name $modulePath 
 Import-Module –Name $adminconsolePath
 Set-Location $siteCode":"  

function CreateMACAddress()
    [int]$len = 12
    [string] $chars = "0123456789ABCDEF"
    $bytes = new-object "System.Byte[]" $len
    $rnd = new-object System.Security.Cryptography.RNGCryptoServiceProvider
    $macraw = ""
    for( $imac=0; $imac -lt $len; $imac++ )
        $macraw += $chars[ $bytes[$imac] % $chars.Length ]
    $macaddress = $macraw[0]+$macraw[1]+":"+$macraw[2]+$macraw[3]+":"+$macraw[4]+$macraw[5]+":"+$macraw[6]+$macraw[7]+":"+$macraw[8]+$macraw[9]+":"+$macraw[10]+$macraw[11]
    Return $macaddress

 function CreateDDREntry()
    param([string]$siteCode, [string]$computerName, [string]$MACAddress)
    [string[]] $macaddresses = $MACAddress
    $agentName = 'CMDeviceSeed Client Generator'
    $ddr = New-Object -typename Microsoft.ConfigurationManagement.Messaging.Messages.Server.DiscoveryDataRecordFile -ArgumentList $agentName
    $ddr.SiteCode = $siteCode
    $ddr.Architecture = 'System'
    $ddr.AddStringPropertyArray('MAC Addresses', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Array, 17, $macaddresses)
    $ddr.AddIntegerProperty('Active', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::None, 1)
    $ddr.AddIntegerProperty('Client', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::None, 1)
    $ddr.AddIntegerProperty('Client Type', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::None, 1)
    $ddr.AddStringProperty('Operating System Name and Version', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::None, 128, "Microsoft Windows NT Workstation 6.3")
    $ddr.AddStringProperty('Name', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Key -bor [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 32, $computerName)
    $ddr.AddStringProperty('Netbios Name', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 16, $computerName)
    Return $true

 for($i=$clientStartNumber; $i -le ($clientCount + $clientStartNumber -1); $i++)
    [string]$mac = CreateMACAddress
    $iformat = "{0:D6}" -f $i
    [string]$name = $clientPrefix+$iformat
    Write-Host "Creating computer"$name "("$mac")"
    Import-CMComputerInformation -ComputerName $name -MacAddress $mac
    CreateDDREntry -siteCode $siteCode -computerName $name -MACAddress $mac

Oh, explaination. Right.
So in the top of the script, I specified the Site Code and that I want to create 10 computers. The script has a numbering function, so I just specify what number to start with, 1 in this case.
Then the prefix of the computers, here 'WS', so the first should be named WS000001 and so on.

Then the path to the .dll and .psd1 files and we are done with the custom part.

You will notice two functions; one for creating a random MAC Address and one for creating the actual computer in SCCM.

We will skip the MAC function, the CreateDDREntry function is more fun.
You can set the Agent Name, it's up to you what to call it. It will be added along the DDR entry.
Architecture is system for a computer.

Then comes the MAC Address some integer properties letting us decide if the computer should act as active and have an agent.
Of cause this is all fake, but still great!
Then we add the Operating System, here 6.3 means Windows 8.1 (yeah, it's been a while since I created this script)
Feel free to create any OS you want 🙂

Did you run it? Sure you did!
Then you will see 10 new very fake devices in the console:

Awesome! You did it.

Want more? How about adding users?

Create users

Absolutely, here is a script for that, just creating some rnadom names:

 $siteCode = 'CT1'
 $domain = 'contoso'

 $userCount = 10
 $userStartNumber = 1
 $firstNames = "Bob","James","John","Frank","William","Peter","Carl","Andreas","Christian","Sally","Joan","Kelly","Jeniffer","Robert","David","Thomas","Kevin","Mary","Linda","Lisa","Maria","Sandra","Sharon","Laura","Kimberly"
 $lastNames = "Smith","Johnson","Williams","Jones","Brown","Davis","Miller","Wilson","Moore","Taylor","Anderson","Jackson","Harris","Lee","Morris","Nelson","Carter","King","Adams","Reed","Bell","Cooper","Gray","James","Ford"

 $modulePath = 'F:\CMDeviceSeed\Microsoft.ConfigurationManagement.Messaging.dll'
 $adminconsolePath = 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
 $addedUsers = New-Object 'System.Collections.Generic.HashSet[string]'
 Import-Module –Name $modulePath 
 Import-Module –Name $adminconsolePath

 function CreateDDREntry()
    param([string]$siteCode, [string]$userName, [string]$fullName)
    $agentName = 'Skylon Client Generator'
    $ddr = New-Object -typename Microsoft.ConfigurationManagement.Messaging.Messages.Server.DiscoveryDataRecordFile -ArgumentList $agentName
    $ddr.SiteCode = $siteCode
    $ddr.Architecture = 'User'
    $ddr.AddStringProperty('User Name', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 32, $userName)
    $ddr.AddStringProperty('Full User Name', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 64, $fullName)
    $ddr.AddStringProperty('Unique User Name', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Key -bor [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 64, $domain.ToUpper() + "\" + $userName)
    $ddr.AddStringProperty('Windows NT Domain', [Microsoft.ConfigurationManagement.Messaging.Messages.Server.DdmDiscoveryFlags]::Name, 32, $domain.ToUpper())
    Return $true

 if ($userCount -gt ($firstNames.Length * $LastNames.Length))
    Write-Host "Max combination is"($firstNames.Length * $LastNames.Length)

 Set-Location $siteCode":"  

 for($i=$userStartNumber; $i -le ($userCount + $userStartNumber -1); $i++)
    #don't add users that already exists
        $rnd = Get-Random -Maximum $firstNames.Length
        $firstName = $firstNames[$rnd]
        $rnd = Get-Random -Maximum $LastNames.Length
        $LastName = $LastNames[$rnd]
        $FullName = $firstName + " " + $LastName
    } while ($addedUsers.Contains($FullName))

    $iformat = "{0:D4}" -f $i
    [string]$name =$firstName.Substring(0,1) + $LastName.Substring(0,1) + $iformat
    Write-Host "Creating user"$firstName $LastName" ("$name")"
    $complexPassword = [System.Web.Security.Membership]::GeneratePassword(14,5)    
    #To create user in the AD, uncomment the next line
    #New-ADUser -Name $FullName -GivenName $firstName -Surname $LastName -SamAccountName $name -AccountPassword $complexPassword
    CreateDDREntry $siteCode $name $FullName

You will notice I commented the New-ADUser out.
If you want (and have permissions) you can add the users to Active Directory too, but do remember to generate a complex password.

But where is the Hardware Inventory part the title promised.
I created a small console utility to push a hardware inventory file to the CM Management Point.

(Why not a PowerShell script? Simple. I am better with C# than with PowerShell, why I created the tool, but I'm sure it can be done with PowerShell too.)

Adding a computer with full hardware inventory

For this you will need to generate a hardware inventory file.
On a computer that has (a real) SCCM Agent open C:\Windows\CCM\Inventory\Temp
Look for the largest xml file.

None there?
Add an empty file named Archives_reports.sms in the folder and force a hardware inventory rerun. Then it should appear.

Open the xml file and verify it is a Hardware Inventory report.
Look for the InventoryAction section, the Description must say Hardware:

Now, copy that xml file to the folder where the file CmDeviceSeed.exe resides. Rename it to 'hwi_template.xml'
Also in that same folder make sure you have the file 'Microsoft.ConfigurationManagement.Messaging.dll'

Open a command prompt and run this command to create a computer and have it upload the hardware inventory:

CmDeviceSeed.exe CT1 -n WS000100 -invfile hwi_template.xml

The tool should output something like this:

CmDeviceSeed.exe CT1 -n WS000100 -invfile hwi_template.xml
Using certificate Site Server Signing Certificate, Thumbprint: 4CC44ECFD72CFA352422849A53AC95C3233CF785
Adding WS000100
Client Edition: Desktop
Registering client in Configuration Manager.
CmDeviceSeed.exe Warning: 0 : No certificate of type ManagementPointSigning was found.
Sending DDR for 'WS000100' to Configuration Manager
Sending Full Hardware Inventory for 'WS000100' to Configuration Manager

Heading over to the console, you will see the computer, but unfortunately there is no hardware inventory 🙁

Why is that, didn't it send it?? Well, yes, but it is the hardware inventory for that other client we just copied it from, so SCCM doesn't accept it.

Hmm, then we will have to open that xml file, search and replace the device name with the one we want to create. But that is how it is.
We can do that with a PowerShell script:

#---------- Variables ----------#
$xmlFilePath = "$PSScriptRoot\hwi_template.xml"
$sitecode = "CT1" #Required
$template_client_name = "CTFAC-T480S" #this is the name of the real client

$global:deviceName = $null
$global:hwInventory = $null
$global:isvGuid = $null

function Set-DeviceName {param([String]$Name)
    Write-Host $Name
    $global:deviceName = $Name
    Write-Host $deviceName
    #Replace the template DeviceName with the name of our device
    $con = Get-Content $xmlFilePath
    $con | % { $_.Replace($template_client_name, $Name) } | Set-Content $xmlFilePath.Replace("hwi_template",$Name)
    #Read content
    [XML]$hwInv = Get-Content –Path $xmlFilePath.Replace("hwi_template",$Name)
    $global:hwInventory = $hwInv

#--------- Execution entry point ----------#
if ([string]::IsNullOrEmpty($sitecode)) 
   Write-Host "You need to set the sitecode variable before running this script"

Set-DeviceName -Name "WS000100"
$invFile = $global:deviceName + ".xml"
.\CmDeviceSeed.exe $sitecode -n $global:deviceName -invfile $invFile -v

So the script replaces the device name with our fake device name, creates a xml file and uploads that along the DDR.

With some luck you should be able to see hardware inventory in the console:

Now that we have the script to create a device, we can just extend that script to create a many devices as we want.
The CmDeviceSeeder util will make sure all fake devices will get a unique hardware id:

Guid guid = Guid.NewGuid();
configMgrRegistrationRequest.HardwareId = string.Concat("2:61A8C54D", guid.ToString().Replace("{", "").Replace("}", "").Replace("-", ""));

We completed the blog, you can now add fake devices with hardware inventory to do your testing.

Wanna do more customization?

Suppose you want to have unique hardware inventory?
Just manipulate the "template" xml file.

Say we want to add some common inventory like manufacturer, model, serialnumber, etc.

Let's add a function to our script that can locate and replace as we want:

function Add-CommonInventory {param([String]$Manufacturer, [String]$ComputerModel, [String]$SerialNumber, [String]$AssetTag, [String]$OperatingSystem, [Long]$PagefileSizeMb)
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_ComputerSystem']//Manufacturer” |
        foreach  { $_.Node.InnerText = $Manufacturer }
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_ComputerSystem']//Model” |
        foreach  { $_.Node.InnerText = $ComputerModel }
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_SystemEnclosure']//Manufacturer” |
        foreach  { $_.Node.InnerText = $Manufacturer }
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_SystemEnclosure']//SerialNumber” |
        foreach  { $_.Node.InnerText = $SerialNumber }
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_SystemEnclosure']//SMBIOSAssetTag” |
        foreach  { $_.Node.InnerText = $AssetTag }
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='Win32_OperatingSystem']//Caption” |
        foreach  { $_.Node.InnerText = $OperatingSystem }
    $PagefileSizeInBytes = $PagefileSizeMb * 1048576
    Select-Xml -Xml $global:hwInventory -XPath “//*[@Class='CCM_LogicalMemoryConfiguration']//TotalPageFileSpace” |
        foreach  { $_.Node.InnerText = $PagefileSizeInBytes }

Set-DeviceName -Name "WS000100"
Add-CommonInventory -Manufacturer "VMware" -ComputerModel "VMware7\x002C1" -SerialNumber "SN00001111" -AssetTag "AT00001111" -OperatingSystem "Microsoft\x0020Windows\x0020Server\x00202019\x0020Enterprise" -PagefileSizeMb 3583

Here we faked the computer to become a Windows 2019 Enterprise server running on VMWare.

But you can add more Disks, Memory, CPU, etc if you want:

function Add-RecentApp {param([String]$ProductName, [datetime]$LastRunDate, [int]$Instances) 
$rnd = Get-Random
[xml]$recentAppXml = @"
    <Instance ParentClass="CCM_RecentlyUsedApps" Class="CCM_RecentlyUsedApps" Namespace="\\$global:deviceName\root\cimv2\sms" Content="New">
  For ($i=$Instances; $i -ge 1; $i--) {
    [xml]$recentAppXmlModified = $recentAppXml.InnerXml.Replace('20010101000000.000000+000',("{0:yyyyMMddhhmmss.ffffff+000}" -f [datetime]$LastRunDate.AddDays(-$i +1)))

function Add-CPU {param([String]$Manufacturer, [String]$Model, [int]$Clockspeed, [int]$NumberOfCores)
[xml]$cpuXml = @"
  <Instance Content="New" Namespace="\\$global:deviceName\root\cimv2\sms" Class="SMS_Processor" ParentClass="SMS_Processor">

function Add-Disk {param([int]$Index, [String]$Manufacturer, [String]$Model, [long]$Capacity)
$capacityInBytes = $Capacity * 1073741824
[xml]$diskXml = @"
<Instance Content="New" Namespace="\\$global:deviceName\root\cimv2" Class="Win32_DiskDrive" ParentClass="Win32_DiskDrive">

function Add-Memory {param([int]$Index, [int]$Type, [String]$Label, [String]$Manufacturer, [String]$SerialNumber, [Long]$Capacity)
$capacityInBytes = $Capacity * 1073741824
[xml]$memoryXml = @"
<Instance Content="New" Namespace="\\$global:deviceName\root\cimv2" Class="Win32_PhysicalMemory" ParentClass="Win32_PhysicalMemory">

Set-DeviceName -Name "WS000100"
Add-CommonInventory -Manufacturer "VMware" -ComputerModel "VMware7\x002C1" -SerialNumber "SN00001111" -AssetTag "AT00001111" -OperatingSystem "Microsoft\x0020Windows\x0020Server\x00202019\x0020Enterpriose" -PagefileSizeMb 3583
Add-CPU -Manufacturer "GenuineIntel" -Model "Intel" -Clockspeed 2000 -NumberOfCores 1
Add-CPU -Manufacturer "GenuineIntel" -Model "Intel" -Clockspeed 2000 -NumberOfCores 1
Add-Disk -Index 0 -Manufacturer "(Standard\x0020disk\x0020drives)" -Model "Disk\x0020Drive" -Capacity 79
Add-Disk -Index 0 -Manufacturer "(Standard\x0020disk\x0020drives)" -Model "Disk\x0020Drive" -Capacity 59
Add-Memory -Index 0 -Type 2 -Label "None" -Manufacturer "VMWare\x0020Virtual\x0020RAM" -SerialNumber "000000001" -Capacity 2
Add-Memory -Index 1 -Type 2 -Label "None" -Manufacturer "VMWare\x0020Virtual\x0020RAM" -SerialNumber "000000002" -Capacity 1
Add-RecentApp  -LastRunDate (Get-Date).AddDays(-1) -ProductName "Exchange Server 2016 Enterprise" -Instances 1
Add-RecentApp  -LastRunDate (Get-Date).AddDays(-1) -ProductName "SQL Server 2014 Enterprise" -Instances 10
$invFile = $global:deviceName + ".xml"
.\CmDeviceSeed.exe $sitecode -n $global:deviceName -invfile $invFile -v

Last thing I wanna add is how to create a User/Device Affinity relation.
We will just add this directly into SCCM using WMI, so different for the above:

function Add-UserDeviceAffinity{ Param($SiteCode, $DeviceName, $UserName ) 
   $AffinityType = 2 # Administrator defined
   $CombinedResource = Get-WmiObject -Namespace "Root\SMS\Site_$SiteCode" -Class "SMS_CombinedDeviceResources" -Filter "Name='$DeviceName'"
   Invoke-WmiMethod -Namespace "root\sms\site_$SiteCode" -Class "SMS_UserMachineRelationship" -Name "CreateRelationship" -ArgumentList @($CombinedResource.ResourceID, $AffinityType, 1, $UserName) 

    Add-UserDeviceAffinity -SiteCode $sitecode -DeviceName "WS000100" -UserName "contoso\bl0010"

I hope you liked this rather long post.


Who am I?

My name is Flemming Appelon Christiansen.
I am a developer at CTGlobal and have a long history with developing tools and apps for Configuration Manager and other management systems.
Currently very focused on our product Insight Analytics that you owe yourself to have a look at.
My experience comes out of interest and curiosity, probably just like yours 🙂