Using Invoke-CimMethod to test if a registry path exists - powershell

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
}

Related

Do the newer CIM cmdlets follow a different protocol for remote systems?

I've been trying to install some remote updates that are in error state (well any updates in general), but continue to get and error of:
No instance found with given property values.
. . that's it. That's the only error that is displayed with no further info given. Looking at the the documents for Invoke-CimMethod, it show's that it expects an argument of: [[-Arguments] <IDictionary>], which is fine, but i'm just not understanding what I may be doing wrong.
Currently I am grabbing the available updates on the remote system. Outputting to Out-GridView for a selection, and using Invoke-CimMethod to try and invoke the installation:
Invoke-CimMethod -Namespace $nameSpace `
-ClassName "CCM_SoftwareUpdatesManager" `
-CimSession $CIMSession `
-Arguments #{
CCMUpdates = [ciminstance[]]$_
} -MethodName "InstallUpdates" -ErrorAction Stop
So far, from other posts like this one, the argument value has to be of CIMInstance; which they are when running .GetType().FullName on the current instance from the pipeline: Microsoft.Management.Infrastructure.CimInstance. . .but, according to that same post with the same error, the issue was that the instances had to not have been saved to a variable? Only way that would make sense is that it needs to not be a de-serialized instance? Either way, I gave their suggestion a try but, still got the same error.
Question: Is there something that I am doing wrong with passing the arguments to the command for it to continue to give me that error?
So here is the rest of the code in hopes that someone smarter than me can help me understand what is the cause here:
Function Install-SCUpdates {
Param (
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[switch]$Force
)
Begin
{
$nameSpace = "root\ccm\clientSDK"
$className = "CCM_SoftwareUpdate"
$EvaluationState = #{
0 = 'None'
1 = 'Available'
2 = 'Submitted'
3 = 'Detecting'
4 = 'PreDownload'
5 = 'Downloading'
6 = 'WaitInstall'
7 = 'Installing'
8 = 'PendingSoftReboot'
9 = 'PendingHardReboot'
10 = 'WaitReboot'
11 = 'Verifying'
12 = 'InstallComplete'
13 = 'Error'
14 = 'WaitServiceWindow'
15 = 'WaitUserLogon'
16 = 'WaitUserLogoff'
17 = 'WaitJobUserLogon'
18 = 'WaitUserReconnect'
19 = 'PendingUserLogoff'
20 = 'PendingUpdate'
21 = 'WaitingRetry'
22 = 'WaitPresModeOff'
23 = 'WaitForOrchestration'
}
}
Process
{
foreach ($Computer in $ComputerName)
{
Try {
$CIMSession = New-CimSession -ComputerName $Computer -ErrorAction Stop
$updatesFound = Get-CimInstance -Namespace $nameSpace -ClassName $className -CimSession $CIMSession
if ($updatesFound) {
if ($Force) {
$null = Invoke-CimMethod -MethodName "InstallUpdates" -Namespace $nameSpace -ClassName "CCM_SoftwareUpdatesManager" -Arguments #{CCMUpdates = [ciminstance[]]$updatesFound} -CimSession $CIMSession
$updatesFound | Select-Object -Property ArticleID,
#{
Name='Status';
Expression = {
$Code = [int]$_.EvaluationState.ToString().Trim()
$EvaluationState.$code
}
}, Name
}
else {
$selectedSoftware = $updatesFound | Select-Object -Property ArticleID,
#{
Name='Status';
Expression = {
$Code = [int]$_.EvaluationState.ToString().Trim()
$EvaluationState.$code
}
}, Name,Description | Out-GridView -Title $Computer -PassThru
if ($selectedSoftware) {
$selectedSoftware = $updatesFound.Where{
$_.Name -in $selectedSoftware.Name
}
$selectedSoftware | ForEach-Object -Process `
{
#$_.GetType().FullName
#$cimInstance = [ciminstance[]](Get-CimInstance -Query "SELECT * FROM $className WHERE updateID='$($_.UpdateID)'" -Namespace $nameSpace -CimSession $CIMSession)
#$cimInstance | gm
#Invoke-CimMethod -MethodName "InstallUpdates" -Namespace $nameSpace -ClassName "CCM_SoftwareUpdatesManager" -Arguments #{CCMUpdates = $cimInstance } -CimSession $CIMSession
#Invoke-CimMethod -InputObject $_ -MethodName InstallUpdates -CimSession $CIMSession
$updateID = $_.UpdateID
Invoke-CimMethod -Namespace $nameSpace `
-ClassName "CCM_SoftwareUpdatesManager" `
-CimSession $CIMSession `
-Arguments #{
CCMUpdates = [ciminstance[]]$_
} -MethodName "InstallUpdates" -ErrorAction Stop
}
$selectedSoftware | Select-Object -Property "ArticleID",
#{
Name='Status';
Expression = {
$Code = [int]$_.EvaluationState.ToString().Trim()
$EvaluationState.$code
}
}, "Name"
}
else {
Write-Host -Object "No software selected to install.`n"
Continue
}
}
}
else {
Write-Host -Object "No updates Found!:)"
}
}
Catch {
Write-Host -Object $_.Exception.Message -ForegroundColor Red -BackgroundColor Black
}
Finally {
if (Get-CimSession) {
Get-CimSession | Remove-CimSession
}
}
}
}
End { }
}
There really shouldn't be no expected output other than that of the command actually running successfully.

Convert WMI call to CIM call

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
}

How do you use PowerShell CIM to get and/or set registry values on a remote computer?

How do you use a PowerShell CIM command to get and/or set registry values on a remote computer? I have verified that the New-CimSession connected properly but I can't find the command(s) to get or set registry values.
You need to call Invoke-CimMethod to invoke the proper method of the StdRegProv WMI class for that, e.g. like this:
$computer = 'remotehost'
$hive = [uint32]'0x80000002' # HKLM
$subkey = 'SOFTWARE\Foo'
$value = 'bar'
$data = 'baz'
Invoke-CimMethod -Computer $computer -Namespace 'root/cimv2' -Class 'StdRegProv' -MethodName 'SetStringValue' -Arguments #{
'hDefKey' = $hive
'sSubKeyName' = $subkey
'sValueName' = $value
'sValue' = $data
}
However, instead of CIM or WMI I would recommend using the proper .Net API:
$computer = 'remotehost'
$hive = 'LocalMachine' # HKLM
$subkey = 'SOFTWARE\Foo'
$value = 'bar'
$data = 'baz'
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive, $computer)
$key = $reg.OpenSubKey($subkey, $true)
$key.SetValue($value, $data, 'String')
You would do it over a PSsession
$RemoteComputer = New-PSsession -Computer CompNamehere
Invoke-Command -Computer $RemoteComputer -ScriptBlock {Set-ItemProperty HKLM:\registrypath}
This will show examples
Get-Help Set-ItemProperty -Full

Options to use different param

I'm looking for a way to to have a choice of a list or a single computername in a foreach loop.
If the user enters in a single computername I want the script to execute for that one computername
but if that user wants to use a path to a list of computers how could I replace $computername with the path that user wants?
function Get-OSInfo {
[CmdletBinding()]
param (
#[Parameter(ValueFromPipeline=$True,
# ValueFromPipelineByPropertyName=$True)]
[string]$computername,
[string]$errorlog = 'c:\errors.txt',
[switch]$logerrors
)
PROCESS {
foreach ($computer in $computername) {
Try {
$os = Get-WmiObject -EA Stop –Class Win32_OperatingSystem –ComputerName $computer
$cs = Get-WmiObject -EA Stop –Class Win32_ComputerSystem –ComputerName $computer
$bios = Get-WmiObject -EA Stop –Class Win32_BIOS –ComputerName $computer
$cpu = Get-WmiObject -EA Stop -class Win32_processor -ComputerName $computer
$props = #{'ComputerName'=$computer;
'OSVersion'=$os.version;
'SPVersion'=$os.servicepackmajorversion;
'OSBuild'=$os.buildnumber;
'OSArchitecture'=$os.osarchitecture;
'Manufacturer'=$cs.manufacturer;
'Model'=$cs.model;
'BIOSSerial'=$bios.serialnumber
'CPU Count'=$CPU.Count
'Memory'= [Math]::round(($cs.TotalPhysicalMemory/1gb),2)
'CPU Speed'= $CPU.MaxClockSpeed[0]}
$obj = New-Object -TypeName PSOBject -Property $props
$obj.PSObject.TypeNames.Insert(0,'Get-OS.OSInfo')
#Write-Output $obj
$obj | Export-Csv c:\test4.csv -Append
} Catch {
if ($logerrors) {
$computer | Out-File $errorlog -append
}
Write-Warning "$computer failed"
}
}
}
}
Change the type of the $ComputerName parameter to a string array instead of just a single string:
param(
[string[]]$ComputerName,
[string]$errorlog = 'c:\errors.txt',
[switch]$logerrors
)
Notice the [] after the type name, this denotes an array of strings, rather than a single string.
Now you can do:
PS C:\> $computers = Get-Content C:\computers.txt
PS C:\> Get-OSInfo -ComputerName $computers
If you'd like to be able to specify a path to a file containing the target computers as the argument to the function, you can use multiple parameter sets:
[CmdletBinding(DefaultParameterSetName='ByName')]
param(
[Parameter(ParameterSetName='ByName',ValueFromPipeline)]
[string[]]$ComputerName,
[Parameter(ParameterSetName='ByFile')]
[string]$InputFile
)
begin {
if($PSCmdlet.ParameterSetName -eq 'ByFile'){
try{
$ComputerName = Get-Content -LiteralPath $InputFile
}
catch{
throw
return
}
}
}
process {
foreach($Computer in $ComputerName){
# Work with $Computer here...
}
}

get-process product version remote computer

If I locally do this I get all the information:
get-process | select-object name,fileversion,company
However, if I do it on a remote computer I only get the process name and all the other fields are blank. Does anyone know why or how to get the same information. I am using a domain admin credential so I should have access to that information.
get-process -computername xcomp123 | select-object name,fileversion,company
You can try this solution:
$Computername = 'Remotehost'
$Session = New-CimSession -ComputerName $Computername
$process = Get-CimInstance -ClassName Win32_Process -CimSession $Session
$col = New-Object System.Collections.ArrayList
foreach ($n in $process){
$exePath = $null
$ExeInfo = $null
$exePath = $n.ExecutablePath -Replace '\\','\\'
$ExeInfo = Get-CimInstance -ClassName Cim_DataFile -Filter "Name = '$exePath'" -ErrorAction SilentlyContinue
[void]$col.add([PSCustomObject]#{
Name = $n.name
FileVersion = $ExeInfo.Version
Company = $ExeInfo.Manufacturer
PSComputername = $n.PSComputername
})
}
Remove-Cimsession $session
$col
Update:
I reduced the code to check for one process only. I assert the referencefile having the same name, as the process on the client computers. You might change that for your needs.
You can specify multiple computers at $computername, so you do not have to run the code over and over again.
#region Reference file
$RefFile = Get-item "\\x123\c$\program files\prog\winagent\file.exe"
#endregion
#region remote file
[string[]]$Computername = 'Remotehost1', 'Remotehost2'
$Processname = $RefFile.Name
foreach ($n in $Computername) {
$Session = New-CimSession -ComputerName $n
$process = Get-CimInstance -ClassName Win32_Process -CimSession $Session -Filter "name = '$Processname'"
$exePath = $process.ExecutablePath -Replace '\\', '\\'
$ExeInfo = Get-CimInstance -ClassName Cim_DataFile -Filter "Name = '$exePath'" -ErrorAction SilentlyContinue
[PSCustomObject]#{
Name = $Processname
FileVersion = $ExeInfo.Version
Company = $ExeInfo.Manufacturer
PSComputername = $n
}
Remove-Cimsession $session
}
#endregion