WMI Generic Failure when attempting to remove software updates from a SCCM deployment package - powershell

I'm using SCCM 2007 and Powershell to automate some mundane tasks in my environment. One of these tasks is to remove all expired or superseded updates in all of my deployment packages. I've went over the Microsoft documentation for the SMS_SoftwareUpdatesPackage WMI class and the RemoveContent method you're supposed to use for this function but keep hitting a wall.
Here's the code I'm currently using.
$x = gwmi SMS_SoftwareUpdatesPackage -computer sccm -namespace root\sms\site_unh -filter "PackageID = 'UNH00277'"
$array = #()
$array += 34827
$x.RemoveContent($array,$true)
Pretty simple, right? I've tried so many different combinations of syntax I'm going crazy. The only lead I've been tracking is that maybe my array datatype isn't right. According to the documentation, it's supposed to be a UInt32 Array but that's just a hunch.

The "solution" for this was to simply set $ErrorActionPreference to SilentlyContinue to skip over this failure. After I added that, it successfully removed all of the updates I wanted.

Related

Powershell 6.2 and Get-CimInstance to remotely stop/start/restart service issues

So I am at a big roadblock right now. I was working in PowerShell 5.1 and had the Get-WmiObject with the Win32_Service Class working perfectly fine to remotely stop/start/restart services remotely. However, I found out that the .StopService() method has been removed in PowerShell 6 (which I used this to bypass the dependencies issues I kept running in to). As well, I have found out that the -ComputerName variable has also been removed from a lot of the different commandlets.
Since the removed the -ComputerName from things like the Stop-Service commandlet, I am struggling to figure out how to properly handle the stopping of the service itself. I can retrieve the service or services I want without issue. I just can't seem to figure out how to handle stopping the service.
Tried being as detailed as I can. I know I am missing something stupidly small, but all of my Google searches appear to return everything with PowerShell 5, but so little on 6.
Thanks.
Ok, nevermind about this question. I decided to approach my Google searching another way and I stumbled upon the syntax I needed. For those of you wondering, you cannot run commands straight from the returned object. So for example, you could do "$service.StopService()" straight on the service previously. However, now you have to do an invoke of that StopService method by doing "Invoke-CimMethod -Name StopService" and that is only after you pipe the Get-CimInstance returned object to it. So the full syntax would look something like this.
get-ciminstance win32_service -filter "Name='spooler'" | Invoke-CimMethod -Name StartService
A really good website I found to explain and give really good examples of why and how the Cim Instances are they way they are.
https://4sysops.com/archives/managing-services-the-powershell-way-part-7

Querying Windows Update Errors using PowerShell Get-WinEvent or Get-WMIObject

Trying to create a simple Windows Update error query using Get-WinEvent (although I would prefer querying a WMI Object for use with SCUP):
get-winevent -logname System| Where-Object {$_.ProviderName -eq "Microsoft-Windows-WindowsUpdateClient"}
This seems to work for the most part. However, it only returns informational events and not errors. Are these located somewhere else and, if so, how would I query them? For some background, there is a specific update failure occurring on approximately 10% of Windows 10 machines in my environment (missing assembly file) and I want to target it so that I can deploy a solution.
A solution using Get-WinEvent is fine, though I would prefer using Get-WMIObject if possible.
You can use the Win32_NTLogEvent like this:
Get-WmiObject Win32_NTLogEvent |?{($_.LogFile -eq 'System') -and ($_.SourceName -eq 'Microsoft-Windows-WindowsUpdateClient') }
Note: You can further filter with Type which will tell you about information or error or warning.
Hope it helps.
I cannot find anything that actually states this but it looks like Get-WinEvent by default only returns information messages. If you want to see the other then you need to tell it to return those. One way to do it is with -FilterHashtable.
Get-WinEvent -FilterHashtable #{LogName='System';Level=1,2}
That would return only warnings and error.
1 - Error
2 - Warning
4 - Information
You can look at the enum [System.Diagnostics.EventLogEntryType] to see where I got the numbers from.
Looking at MS you can see what the hashtable filter supports..
LogName=<String[]>
ProviderName=<String[]>
Path=<String[]>
Keywords=<Long[]>
ID=<Int32[]>
Level=<Int32[]>
StartTime=<DateTime>
EndTime=<DataTime>
UserID=<SID>
Data=<String[]>
*=<String[]>
If your WMI queries are having similar issues then you can do something like this
Get-WmiObject -class Win32_NTLogEvent -filter "(logfile='Application') AND (type='error')"
You can find some tangential examples here
Write a WMI query (this overrides weird event type filters):
Get-WmiObject -Query "Select * from Win32_NTLogEvent" |?{(($_.LogFile -eq 'System') -and ($_.Type -in ("Error", "Warning"))) -and ($_.SourceName -eq 'Microsoft-Windows-WindowsUpdateClient') }
Okay, so after doing some additional research, I stumbled upon this website that sheds some light on the issue I'm running into. Essentially, while most, if not all Windows Events are logged in the C:\Windows\System32\Winevt\logs folder, not all Windows Events are replicated in WMI by default.
In PowerShell, Get-WinEvent appears to use the above folder when querying its event data, whereas Get-EventLog uses the Win32_WinNTLogEvent WMI class.
In my original question, I mentioned that I was unable to query Windows Update error events using Get-WinEvent. This is because I was pointing to the System log file, which does not contain the information. The Microsoft-Windows-WindowsUpdateClient/Operational log file (literal path being C:\Windows\System32\Winevt\logs\Microsoft-Windows-UpdateClient%4Operational.evtx) does contain this information, so my query can simply be changed up using something similar to the following:
Get-WinEvent -logname "Microsoft-Windows-WindowsUpdateClient/Operational" | Where-Object {$_.LevelDisplayName -eq "Error"}
In order to query the same data returned by Get-WinEvent using the Win32_NTLogEvent WMI class, the registry must first be modified. Again, the link I posted in this answer describes the process in greater detail, but essentially I performed the following registry mod:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Microsoft-Windows-WindowsUpdateClient/Operational]
"File"="%SystemRoot%\\System32\\Winevt\\Logs\\Microsoft-Windows-WindowsUpdateClient%4Operational.evtx"
"Primary Module"="Microsoft-Windows-WindowsUpdateClient/Operational"
"Microsoft-Windows-WindowsUpdateClient/Operational"=hex(2):25,00,53,00,79,00,73,00,74,\
00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,\
65,00,6d,00,33,00,32,00,5c,00,77,00,65,00,76,00,74,00,61,00,70,00,69,00,2e,\
00,64,00,6c,00,6c,00,00,00
Note: The "Microsoft-Windows-WindowsUpdateClient/Operational" Expanded String (REG_EXPAND_SZ) at the end there is pointing to %SystemRoot%\system32\wevtapi.dll
Once the registry was modified, I was able to query the error events as follows:
Get-WmiObject -query "SELECT * FROM Win32_NTLogEvent WHERE LogFile='Microsoft-Windows-WindowsUpdateClient/Operational' AND Type='Error'"
Somewhat of a pain considering that Windows Update errors should probably be present in the Win32_NTLogEvent WMI class by default (ah, Microsoft). Still, this essentially resolves my question.
One additional point to mention. The website above states that, upon editing the registry, you'd be able to query the new events immediately. I had to reboot my machine first.

Early filtering within the root\ccm\cliensdk namespace

I recently wrote a script that updates registry values on remote desktops after checking, for instance, that a certain application, MyApp, is properly installed.
The aforementioned application is installed/deployed by SCCM (2012, not R2 for the moment).
In the process of optimizing the script, I wanted to change the test of the install state of MyApp (from late to early filtering).
So far, no luck and so far, no explanation either.
I can't properly understand why it seems not possible to do some early filtering with the following command :
gwmi -ComputerName myserver -Namespace root\ccm\clientsdk -query "select * from ccm_application where Fullname='MyApp'"
Of course, nor can we use :
gwmi -ComputerName myserver -Namespace root\ccm\clientsdk -class ccm_application -filter "Fullname='MyApp'"
Late filtering, of course, works but I wanted (and expected) early filtering to work, especially since I am checking the Install state of an app for quite a lot of remote desktops.
Of course, I do know that I could (can) use SCCM for that purpose (executing a script only if ...) but that still does not explain why I can't do early filtering.
Whenever I try to query that class with my installation while specifying either properties or a filter, I get the error "Provider is not capable of the attempted operation". It doesn't matter if I use Get-WmiObject or Get-CimInstance.
I get the same error when I run this:
PS C:\> WMIC.EXE /NAMESPACE:\\root\ccm\clientsdk PATH ccm_application GET FullName
Node - <SERVERNAME>
ERROR:
Description = Provider is not capable of the attempted operation
PS C:\> wmic /NAMESPACE:\\root\ccm\clientsdk PATH ccm_application WHERE "FullName='Java 32-bit'"
Node - <SERVERNAME>
ERROR:
Description = Provider is not capable of the attempted operation
Although this works just fine:
WMIC.EXE /NAMESPACE:\\root\ccm\clientsdk PATH ccm_application
Seems like a limitation of the provider then, not a problem with your code. -Filter and -Property don't work by design.
Note that I am using 2012 R2 SP1 (5.00.8239.1000), so this may not perfectly apply. However, it seems unlikely that they would remove the functionality from the provider moving from 2012 to 2012 R2.

Powershell - Refresh SNMP from registry or do a SNMPServiceResetEvent

I've written a powershell script that writes registry entries for network drivers to change DCB settings. Things like turning DCB on and off, defining traffic classes and bandwidth groups. After writing the values to registry sometimes you cannot see the changes with SNMP remotely. Though this is inconsistent.
I've scoured the web to see if there is a way to force SNMP to get its values from registry again. We have a script that sets the values in SNMP which automatically changes the registry. I'm trying to go the opposite way and set the values in the registry and have the MIB updated. I've tried reseting the SNMP service and network device in the script with no luck.
After modifying the registry, do a SetEvent on the global event named
"SNMPServiceResetEvent". (That is, do a CreateEvent to that named event and
then do a SetEvent). That should cause the agent to reintialize using the
current registry values.
Jeff Kelley
Microsoft / Windows CE Networking
The only thing I've found that sounds like what I need is the above quote, though futher research suggests he is talking about C++ or C#. Is there a way to do what he suggests in powershell? I found a New-Event commandlet though I'm unsure of its suitability to the current task. Code I've tried:
$snmpService = New-Event -sourceidentifier dcbScriptSnmpReset -sender SNMPServiceResetEvent -messagedata "Reset SNMP to refresh Registry"
Though there is no Set-Event commandlet and I don't know how to proceed.
Any help or leads would be much appreciated.
Thanks,
Marcus
Apparently I misunderstood how SNMP worked and now I can get it to update values entered into the registry by enabling and disabling the network adapter and stopping and starting the SNMP service:
Stop-Service $snmpService.Name
$adaptor = Get-WmiObject -Class Win32_NetworkAdapter | Where-Object {$_.DeviceID -eq $deviceID }
$adaptor.Disable()
$adaptor.Enable()
Start-Service $snmpService.Name

Powershell Remoting Speeding up a Foreach Loop Hosted Exchange

I have a CSV of email adddresses and Departments that I need to set on Live#edu. The command I currently have looks something like this:
Import-CSV departments.csv | ForEach-Object { Set-User $_.EmailAddress $_.Department }`
The problem is, this operation takes FOREVER.
My first thought is that it would be great to have the ForEach-Object command actually be forwarded over to the remote machine, so that it will only need to create the one pipeline between the two machines, but when I go into the PSSession, there doesn't seem to be any foreach-object available. For reference, How I Import the PSSession is:
Import-PSSession(New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri 'https://ps.outlook.com/powershell' `
-Credential (Get-Credential) `
-Authentication Basic -AllowRedirection)
Is there a better way that I can import the session to allow ForEach-Object to be remote, or to import an aliased version of the remote foreach-object, perhaps as ForEach-Object-Remote, or perhaps does anybody have something better to suggest to streamline this process?
UPDATE:
A Couple Things I've tried:
Using the -AsJob switch on the implicitly remoted command.
Import-CSV departments.csv | ForEach-Object { Set-User $_.EmailAddress $_.Department -AsJob }
This, unfortunately, doesn't work because there are throttling limits in place that don't allow the additional connections. Worse than that, I don't even know that anything went wrong until I check the results, and find that very few of them actually got changed.
Importing the ForEach-Object under a different name.
Turns out that adding a prefix is easy as putting -Prefix RS in the Import-PSSession Command to have things like the ForEach-Object from the Remote Session become ForEach-RSObject in the local session. Unfortunately, this won't work for me, because the server I'm connecting to does not does not have the Microsoft.Powershell ConfigurationName available to me.
UPDATE 2: The Set-User cmdlet seems to be Microsoft provided for Live#edu administration. Its purpose is to set User attributes. It is not a script or cmdlet that I am able to debug. It doesn't take pipeline input, unfortunately, so that would not be able to fix the issue.
As Far as I can tell, the problem is that it has to construct and tear down a pipeline to the remote machine every time this command runs, rather than being able to reuse it. The remote ForEach idea would have allowed me to offload that loop to avoid having to create all those remote pipelines, while the -asJob would have allowed them to all run in parallel. However, it also caused errors to fail silently, and only a few of the records actually get properly updated.
I suspect at this point that I will not be able to speed up this command, but will have to do whatever I can to limit the amount of data that needs to be changed in a particular run by keeping better track of what I have done before (keeping differential snapshots). Makes the job a bit harder.
EDIT: Start-Automate left a very useful help, unfortunately, neither of them work. It is my feeling at this point that I won't find a way to speed this up until my provider gives access to more powershell cmdlets, or the exchange cmdlets are modified to allow multiple pipelines, neither of which I expect to happen any time soon. I am marking his answer as correct, despite the ultimate result that nothing helps significantly. Thanks, Start-Automate.
You can speed up your script and also avoid trying to make two connections to the server by the use of the foreach statement, instead of Foreach-Object.
$departments = #(import-csv .\departments.csv)
foreach ($user in $departments) {
Set-User $user.EmailAddress $user.Department
}
If you need to batch, you could use the for statement, moving forward in each batch
for ($i =0; $i -lt $departments.Count; $i+=3) {
$jobs = #()
$jobs+= Invoke-Command { Set-User $departments[$i].EmailAddress $departments[$i].Department } -AsJob
$jobs+= Invoke-Command { Set-User $departments[$i + 1].EmailAddress $departments[$i + 1].Department } -AsJob
$jobs+= Invoke-Command { Set-User $departments[$i + 2].EmailAddress $departments[$i + 2].Department } -AsJob
$jobs | Wait-job | Receive-job
}
Hope this helps