Still having an old SCCM 2007 server managing some desktops, I came across an interesting problem this week with the “Run Advertised Programs” Control Panel applet.  I’ll call it RAP from here-on!  It’s not a feature we ever made use of, preferring the completely silent automated approach, but wanted to try it this week for something specific.

There’s an app where we needed the user to control when it installed, so this made for an easy solution.  Except trying to get users to find the RAP icon (especially with their having to find the Control Panel and then switch out of Category View first), we decided to simplify things by throwing an icon on the Desktop to it on the machines that we’ll target for this app and then the installation will remove that shortcut afterwards.

I noticed that when the user starts off the download and install from within RAP, they get no on-screen notification that it’s completed, and the main RAP window does not update the “Last Run Time” and “Last Status” fields unless you close and re-open it.  So I thought a simple solution would be to get the installer batch file to close the RAP window to help signify completion rather than having to start faffing with pop-up message boxes (which no one ever reads).  Except it wasn’t actually that simple…

First off, being a Control Panel applet, RAP is a .cpl file and only appears in the process list as an instance of rundll32.exe.  You can’t just close those at random without knowing which is the one you want.  The apparent answer came from a search online.  You can use tasklist.exe to find RAP’s Module (SMSRAP.cpl), limiting its search to rundll32.exe instances using the filter argument:

tasklist.exe /M SMSRAP.cpl /FI "IMAGENAME eq rundll32.exe"

1-Tasklist-x86

Then knowing the Process ID (PID), from there you can go on to close that process.  Most of our kit is 64-bit Windows 10, and here is the output on that OS:

2-Tasklist-x64

The first place to visit to see what was going on was Sysinternals Process Explorer:

3-ProcExp-Standard-x64

Here you can see the rundll32.exe instance containing modules that include the relevant SCCM client components.  The complication of using the SCCM 2007 Client is that it is 32-bit.  You can see from the above that these modules are associated with a 32-bit instance of rundll32.exe whose parent process is a 64-bit instance of the same.

Next thing was to try a similar approach in PowerShell.  Get-Process has a -Module parameter that lists the modules loaded in a process. For simplified output, I’ve determined the Process ID beforehand.  Here’s the result on a 32-bit Windows 7 OS while RAP is open:

Get-Process -Id 6872 -Module

4-GetProcess-x86

That shows an extensive list of modules which did include all the SCCM related ones. Here’s the equivalent on a 64-bit Windows 10 OS:

5-GetProcess-x64

That’s a very short list and is missing the SCCM components and almost everything else too.  That explains why the tasklist.exe method wasn’t working either.  The DLLs it does include though are telling – of the five listed, three are directly related to WoW64 which is the Windows-on-Windows subsystem that presents a 32-bit environment that allows 32-bit code to run on a 64-bit machine.

Back to Process Explorer, here is a closer look at the loaded modules on the 64-bit machine, this time with the bitness (“Image Type”) column added:

6-ProcExp-Bitnesses-x64

Ordered by bitness, we see the only 64-bit modules in that 32-bit rundll32.exe process are the ones we could see from PowerShell.  So in essence, the problem is that the 32-bit modules – which includes the RAP files, are invisible to the 64-bit techniques we are using.  Now there are ways around it using other tools – there are references online to using Sysinternals ListDlls.exe for example.  I wanted to stay native though.

I decided the easiest thing to do would be to call the 32-bit version of PowerShell and pass it a command.  Here’s example code that queries a specific process (by ID) and returns the modules – filtered to just the ones with “ccm” in their name.  Note I’m using the simplified Where-Object syntax that appeared first in PowerShell v3 in this example:

c:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe -ExecutionPolicy Bypass -Command "Get-Process -Id 8172 -Module | Where FileName -like '*ccm*'"

7-Solution

As you can see, that now means we can get visiblity of these modules.  Coming back to using this in practice in an install script, we wouldn’t know the process ID in advance.
Here then is an example command to call the 32-bit PowerShell instance that locates the rundll32.exe instance that RAP is running in and then closes it.  SUCCESS!

c:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "Get-Process -Name Rundll32 -ErrorAction SilentlyContinue | Where-Object {$_.Modules.ModuleName -contains 'SMSRAP.cpl' } | Stop-Process -Force"

The -ErrorAction SilentlyContinue on Get-Process prevents errors if there are no instances of rundll32 at all.

$_.Modules gets the modules property of the current process in the pipeline coming from Get-Process.  If you look with Get-Member, you will see this property is of type System.Diagnostics.ProcessModuleCollection and corresponds to the list of Modules seen in the previous screenshot.  If you pipe the contents of that property to Get-Member, you will find that the collection is composed of objects of type System.Diagnostics.ProcessModule.  We need to be able to filter on the ModuleName property of these objects to find “SMSRAP.cpl”.

Since PowerShell v3, it’s been possible to do this easily thanks to the ability to retrieve a particular property of the members of a collection using Member Enumeration syntax.  In this case, $_.Modules.ModuleName returns an array containing just the ModuleName properties of each item in the collection held in the Modules property of the process.  It’s equivalent to doing ($_.Modules | Select-Object -ExpandProperty ModuleName) which would be be the PowerShell v2-compatible way of doing it!

Note also the use of the -contains operator to see if the collection on the left of the operator contains the value on the right.  There’s also exists a -notcontains operator.  Incidentally, PowerShell v3 also saw the introduction of the complementary -in and -notin operators which work the same way but expect the value on the left and the collection on the right.