Important Notice: On February 29th, this community was put into read-only mode. All existing posts will remain but customers are unable to add new posts or comment on existing. Please feel to join our Community Discord for any questions and discussions.

Scanning for Python


since version 3.5, the packagers of the official Python distribution for Windows have been going out of their way to do everything wrong about packaging that they possibly could.

  • Instead of a single MSI package, they ship an executable wrapper around 22 of them.
  • The ARP entries for the individual packages are created as hidden (by declaring them "system components")
  • The "main" ARP entry is always left in HKCU of the executing user, even for a per-machine installation, therefore it will not show up in ARP for any other user.
  • The uninstall executable is put into the executing user's profile. If that profile is deleted, uninstallation becomes very difficult (you have to remove each individual MSI package by hand).

The third point above is what gives me the most trouble right now, because Inventory does not see the installed package either, even when it was installed by Deploy using the same account on the target.

Is there something I can do to make Inventory see the ARP entry in the user profile (of the Deploy user, the same as the Inventory scanner) rather than HKLM?

(There is a workaround, but it is ugly: I can install the individual MSIs directly, and they will properly register in HKLM, and will not be hidden. The downside is that this will result in between 8 (for a normal installation) and 43 (all packages for x86 and x64) ARP entries. They also must be upgraded in lockstep, and in a particular order, which, fortunately, Deploy can do. The whole approach is documented as unsupported, though.)





Date Votes
  • The scan is performed by the target service running as SYSTEM. It does not detect the Python installation because its Uninstall key is in HKCU of the user that Deploy used to install it, and that user profile is not typically loaded. If, however, the user profile _is_ loaded, Inventory finds the installation just fine.

    So how can I make sure that the Deploy target user's profile is loaded (as in, HKU\S-1-* exists) when Inventory runs?

  • Hey Christian,

    I am no python expert, and I might be misunderstanding your post, but I am asked to install it in our labs frequently.

    When installing 3.5, I use the .exe installer.  It ends up looking something like this...

    "\\NetworkPath\python-3.5.1-amd64.exe" /quiet InstallAllUsers=1

    Is that comparable to how you are installing it?


  • I use SCCM for software/hardware inventory.  We have not moved over to PDQ Inventory, so I am unfamiliar with it at this point.  However, with my installation method, SCCM is able to pick up on the installation of Python and I can run queries for install/update collections.  Maybe I am just misunderstanding your issue, but I figured I would throw out my installation method to see if that would help at all.

  • If you add this as a PowerShell step to your Python installer package it will move the Registry entries and the uninstaller to the standard locations. You can also run it as a separate package to clean up existing installations.

    Note: The uninstaller does not delete these once you have moved them. It's not a problem, it's just stuff that gets left behind.

    $Uninstall_Entries = Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" | Where-Object { $_.DisplayName -like "Python*" }

    ForEach ( $Uninstall_Entry in $Uninstall_Entries ) {

        $GUID = $Uninstall_Entry.PSChildName
        $Registry_Path = "Software\Microsoft\Windows\CurrentVersion\Uninstall\$GUID"
        Write-Output "GUID: $GUID"

        # Move the Registry entries to HKEY_LOCAL_MACHINE
        Move-Item "HKCU:\$Registry_Path" "HKLM:\$Registry_Path"
        $Uninstall_Entry = Get-ItemProperty "HKLM:\$Registry_Path"

        # Move the uninstaller to the normal global location
        $Uninstaller_Path = "$env:LOCALAPPDATA\Package Cache\$GUID"
        Move-Item "$Uninstaller_Path" "$env:SystemRoot\Installer\$GUID"

        # Update the Registry entries with the new path of the uninstaller
        ForEach ( $Property in "BundleCachePath", "DisplayIcon", "QuietUninstallString", "UninstallString" ) {

            $Updated_Property = $Uninstall_Entry.$Property.Replace("$env:LOCALAPPDATA\Package Cache", "$env:SystemRoot\Installer")
            Set-ItemProperty -Force -Path "HKLM:\$Registry_Path" -Name "$Property" -Value "$Updated_Property"


  • Colby, thank you for this idea. I think it would be better to copy the entries than to move them, so that the originals are still there for an upgrade installation to notice. In principle, since it's all MSI under the hood anyway, it should work regardless.

    Jared, yes, that is the same as what I am doing. The only difference is that I have the "InstallAllUsers" in the optional XML file rather than on the command line. I don't know how SCCM manages to see the existing installation, but my guess is that it does deployment and inventory as the same user. [Update: Or rather, that SCCM inventories as the same user PDQ Deploy installs as.]

    There has also been a Python bug about this HKLM/HKCU confusion, in which I seem to be tilting at windmills because nobody else considers it important at all:

  • You're right, copying would be a better idea. Also, you can use the original installer to uninstall, so if the uninstaller gets deleted it shouldn't matter.

    One thing I forgot to mention is that when you uninstall you need to delete the copied/moved registry entry manually or it will look like Python is still installed.

  • So I did something else entirely. I wrote a minimal Windows service that does nothing but wait for the Inventory scan service to stop before stopping itself. The key is that this service is configured to run as the same user that Deploy uses for installations, so starting the service loads the appropriate user profile, and Inventory can then scan it.

    One scheduled task later, my service starts instantly when Inventory begins a scan (Service Control Manager event 7045, "new service installed", with a filter that matches only the PDQ Inventory scanner service).

    This works absolutely perfectly, at least on 7 and 10. As of now, my service has won the race condition between it starting and Inventory scanning for applications every single time (out of perhaps 50). I don't think there can be a proper synchronization between the two because Inventory only installs its service when it wants to scan and deletes it immediately after the scan; this makes it impossible to make the scanner service dependent on mine.