Convert WMI call to CIM call - powershell

The code I am writing is suppose to kick off any patches currently available to a server using CIM. And I have to use CIM due to the required DCOM protocol for my network.
I'm using ` for easier viewing
The following wmi code works:
$ComputerName = 'Foo'
[System.Management.ManagementObject[]] $CMMissingUpdates = #(`
Get-WmiObject -ComputerName $ComputerName `
-Query "SELECT * FROM CCM_SoftwareUpdate WHERE ComplianceState = '0'" `
-Namespace "ROOT\ccm\ClientSDK" `
-ErrorAction Stop)
$null = (Get-WmiObject -ComputerName $ComputerName `
-Namespace "root\ccm\ClientSDK" `
-Class "CCM_SoftwareUpdatesManager" `
-List).InstallUpdates($CMMissingUpdates)
What I've made using CIM that doesn't work:
$null = (Invoke-CimMethod -CimSession $Computer.CimSession `
-Namespace 'ROOT\ccm\ClientSDK' `
-ClassName 'CCM_SoftwareUpdatesManager' `
-MethodName 'InstallUpdates').InstallUpdates($CMMissingUpdates)
Not only am I interested in a solution to my Invoke-CimMethod but how it was solved. I can't seem to determine how to view and implement the methods of classes in CIM.

Your problem is you're using two incompatible commands to translate.
Invoke-CimMethod == Invoke-WmiMethod
Get-WmiObject is not the above, however. Here's a way to accomplish what you're doing:
$ComputerName = 'Foo'
$cimArgs = #{
'Namespace' = 'Root\CCM\ClientSDK'
'ClassName' = 'CCM_SoftwareUpdatesManager'
'MethodName' = 'InstallUpdates' # returns UInt32 object; 0 = success
'Arguments' = #{
'CCMUpdates' = Get-WmiObject -Namespace Root\CCM\ClientSDK -Class CCM_SoftwareUpdate -Filter 'ComplianceState = "0"'
}
'CimSession' = New-CimSession -ComputerName $ComputerName -SessionOption (New-CimSessionOption -Protocol Dcom)
}
Invoke-CimMethod #cimArgs
The Invoke-CimMethod cmdlet takes a dictionary to pass arguments to the method. I determined the keys/values based on this documentation.
This can alternatively be found by the following:
Get-CimClass -ClassName 'CCM_SoftwareUpdatesManager' -Namespace 'Root\CCM\ClientSDK' |
ForEach-Object -MemberName CimClassMethods

Turns out it was a casting issue. Link to solution: https://www.reddit.com/r/PowerShell/comments/8zvsd8/kick_off_a_sccm_clients_install_all_available/
The final solution:
$CMMissingUpdates = #( `
Get-CimInstance -Query "SELECT * FROM CCM_SoftwareUpdate WHERE ComplianceState = '0'" `
-Namespace "ROOT\ccm\ClientSDK"
)
Invoke-CimMethod -Namespace 'ROOT\ccm\ClientSDK' `
-ClassName 'CCM_SoftwareUpdatesManager' `
-MethodName 'InstallUpdates' `
-Arguments #{
CCMUpdates = [cminstance[]]$CMMissingUpdates
}

Related

Invoke-WMIMethod - Throws Error Not Found - SCCM CCM Actions

Invoke-WMIMethod -ComputerName $computer -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000026}" | Out-Null
Invoke-WMIMethod -ComputerName $computer -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000027}" | Out-Null
Invoke-WMIMethod -ComputerName $computer -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-0000000000113}" | Out-Null
Invoke-WMIMethod -ComputerName $computer -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-0000000000108}" | Out-Null)
All throw Not Found Errors.
Any ideas? Are these superseded?
{00000000-0000-0000-0000-000000000026} and {00000000-0000-0000-0000-000000000027} are user specfic and seem to have been changed to a different class that is sid dependent so that you can invoke it for any user. The most common example to call them is
$sched=([wmi]"root\ccm\Policy\$sid\ActualConfig:CCM_Scheduler_ScheduledMessage.ScheduledMessageID='{00000000-0000-0000-0000-000000000026}'")
$sched.Triggers=#('SimpleInterval;Minutes=1;MaxRandomDelayMinutes=0')
$sched.Put()
Where $sid is the users SID with each - replaced with a _
You can get them for logged on users like this:
$sids = (get-wmiobject -query "SELECT UserSID FROM CCM_UserLogonEvents WHERE LogoffTime = NULL" -namespace "ROOT\ccm").UserSID.replace('-','_')
However {00000000-0000-0000-0000-000000000113} and {00000000-0000-0000-0000-000000000108} should still work with the method you used. The only reason to throw the Not Found error for those is if they are not executed with elevated admin rights, but that is the same for all the other TriggerSchedule messages as well so I don't know what the problem could be.

Replace Get-WmiObject with Get-CimInstance

We're reviewing our code and are trying to replace the WMI CmdLets with the CIM ones. The following code works fine:
$Query = "SELECT InstalledLocation,ProductVersion,ProductName FROM SMS_R_System
JOIN
SMS_G_SYSTEM_Installed_Software on SMS_R_System.ResourceID =
SMS_G_SYSTEM_Installed_Software.ResourceID
WHERE SMS_R_SYSTEM.Name=""$C"" "
Get-WmiObject -ComputerName $SCCMServer -Namespace $SCCMNameSpace -Query $Query
Because WMI uses DCOM by default we thought it was as easy as using the following instead:
$CimSessionOption = New-CimSessionOption –Protocol DCOM
$CimSession = New-CimSession -ComputerName $SCCMServer -SessionOption $CimSessionOption
Get-CimInstance -CimSession $CimSession -Namespace $SCCMNameSpace -Query $Query
But we receive the error New-CimSession : Access denied although we're using the same connection protocol.
Is there something obvious we're missing here?

Using Invoke-CimMethod to test if a registry path exists

I want to convert
$path = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
$results += if (Test-Path $path) { $true }
into an Invoke-CIMMethod that I can run when connected to a remote server using a CIMSession.
The below code checks the multi string value of a registry entry. I am assuming I would use something close to this but I am unsure what method I should use for just testing if the path exists.
$Arguments = #{
hDefKey = [uINT32]2147483650;
sSubKeyName = "SYSTEM\CurrentControlSet\Control\Session Manager";
sValueName = "PendingFileRenameOperations"
}
Invoke-CimMethod -ClassName 'StdRegProv' -CimSession $CimSession -MethodName 'GetMultiStringValue' -Namespace 'ROOT\CIMv2' -Arguments $Arguments
You could invoke the EnumKey method against the key in question and then inspect the ReturnValue:
$Arguments = #{
hDefKey = [uint32]2147483650
sSubKeyName = "SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending"
}
$enumKey = Invoke-CimMethod -ClassName 'StdRegProv' -CimSession $CimSession -MethodName 'EnumKey' -Namespace 'ROOT\CIMv2' -Arguments $Arguments
if($enumKey.ReturnValue -eq 2){
# key not found
}

Getting StdRegProv class in Powershell

The only way I can find to get an instance of the StdRegProv WMI class in Powershell is
$StdRegProv = Get-WmiObject -List -Namespace root\default -ComputerName "my_computer" -Credential $cred | where { $_.Name -eq "StdRegProv" }
I need to go this route because I want to supply a credential. Naively I would have expected the following to work:
$StdRegProv = Get-WmiObject -Class StdRegProv -Namespace root\default -ComputerName "my_computer" -Credential $cred
but it doesn't - why can't you get at StdRegProv in this way?
As far as I understand, Get-WmiObject returns existing instances of a class. If there are no instances, you get an empty collection. (But don't quote me on this because it's just a guess - I haven't looked at Get-WmiObject code.)
Anyway, you can use:
[WMIClass]"root\default:StdRegProv"
to instantiate the class. Or, if it has to be gwmi:
Get-WmiObject -List -Namespace "root\default" -ComputerName "my_computer" `
| Where-Object {$_.Name -eq "StdRegProv"}
Source: Powershell remote registry access via WMI.

Is there a bug in Powershell Remoting when reading IIS6 website ServerBindings via WMI?

I am using Powershell Remoting to get a list of websites on IIS6 via WMI (the client machine is Windows 7 64-bit and the server is Windows 2003)
The problem is that following does not work:
$websites = Invoke-Command -ComputerName SEVER_NAME -UseSSL { Get-WmiObject -ComputerName SEVER_NAME -Namespace 'root\MicrosoftIISv2' -Class IISWebServerSetting }
$websites[0].ServerBindings[0].Port
Port is $null, but
$websites[0].ServerBindings.Count
returns 1
I can use this workaround, but it's not conveniant:
$websites = Invoke-Command -ComputerName SEVER_NAME -UseSSL {
Get-WmiObject -ComputerName SEVER_NAME -Namespace 'root\MicrosoftIISv2' -Class IISWebServerSetting |
Select-Object ServerComment, #{
Name="ServerBindingPorts"; Expression =
{
$ports = #();
foreach ($tmpServerBinding in $_.ServerBindings)
{
$ports += $tmpServerBinding.Port
};
return $ports;
}
},#{
Name="SecureBindingPorts"; Expression =
{
$ports = #();
foreach ($tmpSecureBinding in $_.SecureBindings)
{
$ports += $tmpSecureBinding.Port
};
return $ports;
}
}
}
then I can just get ports this way:
$websites[0].ServerBindingPorts
I have the same problem with SecureBindings property.
Is this a bug or am I missing something?
I'm am powershell newbie but I did google for a few hours and found no answer to my problem.
WMI and the WMI cmdlets already do remoting, that's the -server parameter on Get-WMIObject so your:
$websites = Invoke-Command -ComputerName SEVER_NAME -UseSSL { Get-WmiObject -ComputerName SEVER_NAME -Namespace 'root\MicrosoftIISv2' -Class IISWebServerSetting }
can be done equally with:
$websites = Get-WmiObject -ComputerName SEVER_NAME -Namespace 'root\MicrosoftIISv2' -Class IISWebServerSetting
This will avoid needing to go through the WS-MAN remoting serialiser, which may well help.
Also:
Port is $null
What is Port? There is no Port property on IIsWebServerSetting.
And also your custom property expression is very complicated:
#{
Name="SecureBindingPorts"; Expression =
{
$ports = #();
foreach ($tmpSecureBinding in $_.SecureBindings)
{
$ports += $tmpSecureBinding.Port
};
return $ports;
}
}
Rather better to use PowerShell's ability to process lists with pipelines:
#{Name="SecureBindingPorts"; Expression = { #($_.SecureBindings | %{ $_.Port }) }}
where I'm using #(...) to ensure I always get a collection even if there are zero or one objects in the SecureBindings property.