Changing Program Run Type with PowerShell – WMI Program Flags Explained

Hi there and welcome back on my blog, today I will talk about WMI Program Flags, more particular about how to change the Program Run Type with PowerShell and WMI. If you are not familiar with WMI and Program Flags, read this blog post carefully and try to understand what I do. It might look complicated, but once you understand the mechanics it’s not that hard. I would like to say thanks to Kim for explaining this to me in the first place Knipogende emoticon

If you take a look to the properties of a program, on the Environment tab, you notice that we have 3 different Run Types.

image

  • Only when a user is logged on
  • Whether or not a user is logged on
  • Only when no user is logged on

First of all it’s important to know that you won’t be able to change the value from, for example Only when a user is logged on to Whether or not a user is logged on by turning 1 bit on and 1 bit off.

Now let’s start with the creation of a standard package. and create a standard program that we will use as an example. Below the configuration of the package and the standard program.

image

The Package is created, so lets have a look to the properties of the program. We see that the program is configured to run only when a user is logged on. By default the program will also run with user rights.

image

The next step is to gather the Program Flag in WMI. There are a few ways to do this, for example with wbemtest. If you use wbemtest, you will need to do some calculations to obtain the binary value of the Flag. For that reason I prefer using PowerShell.

Open PowerShell and get the Program Flag value by running the script below. Don’t forget to change the PackageID and WMI NameSpace SiteCode.

The script will query for the program in WMI based on the PackageID and will directly convert the ProgramFlag to a binary value.

$PackageID = "CSQ00203"
$PackageQuery = Get-WmiObject -Namespace "Root\sms\Site_CSQ" -Class SMS_Program -ComputerName localhost -Filter "PackageID='$PackageID'"
$ProgramFlagBin = [Convert]::ToString($($PackageQuery.ProgramFlags), 2)
$ProgramFlagBin

In the result you will see the ProgramFlag displayed in binary,  now let’s have a deeper look in the binary switches.

image

Every single bit correspond with a different setting. Below I translated the bits so that we can see what is actually enabled in our Program.

The binary value for our Standard Program is 1000 1000 0001 0000 0100 0100 0000 0000

In the list below, I created a 3rd column where I have put the Binary Values of the Standard Program, to let you see what Flags are enabled. As you probably already noticed and most probably should know is that we start from right to left     <——–

Hex (Bit) Description bit
0x00000001 (0) AUTHORIZED_DYNAMIC_INSTALL. The program is authorized for dynamic install. 0
0x00000002 (1) USECUSTOMPROGRESSMSG. The task sequence shows a custom progress user interface message. 0
No Function / 0
No Function / 0
0x00000010 (4) DEFAULT_PROGRAM. This is a default program 0
0x00000020 (5) DISABLEMOMALERTONRUNNING. Disables MOM alerts while the program runs. 0
0x00000040 (6) MOMALERTONFAIL. Generates MOM alert if the program fails. 0
0x00000080 (7) RUN_DEPENDANT_ALWAYS. If set, this program’s immediate dependent should always be run. 0
0x00000100 (8) WINDOWS_CE. Indicates a device program. If set, the program is not offered to desktop clients. 0
0x00000200 (9) This value is not used. 0
0x00000400 (10) COUNTDOWN. The countdown dialog is not displayed. 1
0x00000800 (11) FORCERERUN. This value is not used. 0
0x00001000 (12) DISABLED. The program is disabled. 0
0x00002000 (13) UNATTENDED. The program requires no user interaction. 0
0x00004000 (14) USERCONTEXT. The program can run only when a user is logged on. 1
0x00008000 (15) ADMINRIGHTS. The program must be run as the local Administrator account. 0
0x00010000 (16) EVERYUSER. The program must be run by every user for whom it is valid. Valid only for mandatory jobs. 0
0x00020000 (17) NOUSERLOGGEDIN. The program is run only when no user is logged on. 0
0x00040000 (18) OKTOQUIT. The program will restart the computer. 0
0x00080000 (19) OKTOREBOOT. Configuration Manager restarts the computer when the program has finished running successfully. 0
0x00100000 (20) USEUNCPATH. Use a UNC path (no drive letter) to access the distribution point. 1
0x00200000 (21) PERSISTCONNECTION. Persists the connection to the drive specified in the Drive Letter property. The USEUNCPATH bit flag must not be set. 0
0x00400000 (22) RUNMINIMIZED. Run the program as a minimized window. 0
0x00800000 (23) RUNMAXIMIZED. Run the program as a maximized window. 0
0x01000000 (24) HIDEWINDOW. Hide the program window. 0
0x02000000 (25) OKTOLOGOFF. Logoff user when program completes successfully. 0
0x04000000 (26) RUNACCOUNT. This value is not used. 0
0x08000000 (27) ANY_PLATFORM. Override check for platform support. 1
0x10000000 (28) STILL_RUNNING. This value is not used. 0
0x20000000 (29) SUPPORT_UNINSTALL. Run uninstall from the registry key when the advertisement expires. 0
0x40000000 (30) The platform is not supported. 0
0x80000000 (31) SHOW_IN_ARP. This value is not used. 1

Now that we filled in the value’s in the table above, we can see that bit 14: USERCONTEXT. The program can run only when a user is logged on is enabled. In order to understand what happens with the bits, let us manually change the Program Run Type to Whether or not a user is logged on and compare the 2 binary value’s. Notice in the print screen below, that also the Run Mode is changed to Run with administrative rights (bit 15)

image

To get the binary value of the Program Flag, we can run the same script again that we used before. Again, make sure that you change the PackageID and WMI NameSpace SiteCode.

$PackageID = "CSQ00203"
$PackageQuery = Get-WmiObject -Namespace "Root\sms\Site_CSQ" -Class SMS_Program -ComputerName localhost -Filter "PackageID='$PackageID'"
$ProgramFlagBin = [Convert]::ToString($($PackageQuery.ProgramFlags), 2)
$ProgramFlagBin

Let’s compare the 2 different settings. The blue rows are untouched and the red rows have changed.

Bit Only Run when a user is logged on Whether or not a user is logged on
0 0 0
1 0 0
2 0 0
3 0 0
4 0 0
5 0 0
6 0 0
7 0 0
8 0 0
9 0 0
10 1 1
11 0 0
12 0 0
13 0 1
14 1 0
15 0 1
16 0 0
17 0 0
18 0 0
19 0 0
20 1 1
21 0 0
22 0 0
23 0 0
24 0 0
25 0 0
26 0 0
27 1 1
28 0 0
29 0 0
30 0 0
31 1 1

If you look in the table we see that 3 values actually have been changed in order to swap the Program Run type from only when a user is logged on to whether or not a user is logged on.

  • Bit 13: From 0 to 1
  • Bit 14: From 1 to 0
  • Bit 15: From 0 to 1

At this stage, we know that 3 bits have changed. Now how do we need to do that with PowerShell, because it’s a pretty boring task to do that manually for 1000+ Programs.

The script below will change the ProgramRunType from all the programs under a specific package. To change the bits, we use bitwise operators.

Bitwise operators act on the binary format of a value. For example, the bit structure for the number 10 is 00001010 (based on 1 byte), and the bit structure for the number 3 is 00000011. When you use a bitwise operator to compare 10 to 3, the individual bits in each byte are compared.

In a bitwise AND operation, the resulting bit is set to 1 only when both input bits are 1.

1010   (10)

0011   (  3)

—————— band

0010    (  2)

In a bitwise OR (inclusive) operation, the resulting bit is set to 1 when either or both input bits are 1. The resulting bit is set to 0 only when both input bits are set to 0.

1010   (10)

0011   (  3)

—————— bor (inclusive)

0010    (11)

In a bitwise OR (exclusive) operation, the resulting bit is set to 1 only when one input bit is 1.

1010   (10)

0011   (  3)

—————— bxor (exclusive)

0010    (  9)

Let us recap the above in human language:

  • to check if a certain flag is enabled in WMI we will use –band
  • to change 1 bit from 0 to 1 we will use –bor
  • to change 1 bit from 1 to 0 we will use –bxor

This being said, let’s have a look to the actual script to change the ProgramRunType from Only when a user is loggod on to Whether or not a user is logged on. The script is using a input file PackageID.txt (containing only PackageIDs) that needs to be placed in the same folder as the script. The Script will create in the same folder a logfile that is readable with CMTrace.

# ===============================================================================================
# 
# NAME: Change Program Run Type
# 
# AUTHOR: Ken Goossens
# DATE  : 28/01/2016
# 
# COMMENT: The Script will change The Program Run Type for all Install and Remove Programs
#             - Changing from Only when a user is logged on to Whether or not a user is logged on
#
# ===============================================================================================

# ============================================ FUNCTIONS ========================================

# Global Variables
$Logfile = "$PSScriptRoot\CL_ChangeProgramRunType.log"

Function Write-Log {

        PARAM(
             [String]$Message,
             [String]$Path = $Logfile,
             [int]$severity,
             [string]$component
             )
             
             $TimeZoneBias = Get-WmiObject -Query "Select Bias from Win32_TimeZone"
             $Date= Get-Date -Format "HH:mm:ss.fff"
             $Date2= Get-Date -Format "MM-dd-yyyy"
             $type=1
             
             "<![LOG[$Message]LOG]!><time=$([char]34)$date+$($TimeZoneBias.bias)$([char]34) date=$([char]34)$date2$([char]34) component=$([char]34)$component$([char]34) context=$([char]34)$([char]34) type=$([char]34)$severity$([char]34) thread=$([char]34)$([char]34) file=$([char]34)$([char]34)>" | Out-File -FilePath $Path -Append -NoClobber -Encoding default
}

Function CreateLogFile {  
        If (Test-Path $Logfile){
            Write-Log -severity 1 -component "-------------------------------------" -Message "------------------------------------------------------------------------------"
            Write-Log -severity 1 -component "Checking LogFile" -Message "-- LogFile Exists"
            }

        Else{
            New-Item $Logfile -type file
            Write-Log -severity 1 -component "Checking LogFile" -Message "-- LogFile Created"
            }
}

Function GetSMSSiteCode {
    $SMS_ProviderLocation = Get-WMIObject -query "Select * From SMS_ProviderLocation Where ProviderForLocalSite = true" -Namespace "root\sms" -ComputerName "." -ErrorAction Stop
    $SMS_SiteCode = $SMS_ProviderLocation.SiteCode
    Return $SMS_SiteCode
}

# ========================================= END OF FUNCTIONS =====================================

# Check LogFile
$CheckLogFile = CreateLogFile

# Set Variables
$SMS_SiteCode = GetSMSSiteCode

# Reading a list of PackageIDs
$PackageIDs = Get-Content $PSScriptRoot\PackageID.txt

# Loop through all the Packages in the input list.
Foreach($PackageID in $PackageIDs){

    Write-Log -severity 2 -component "Start Processing Package" -Message "---- Start to process package with PackageID: $PackageID."

    # Query all the programs under the specific packageID
    $PackageQuery = Get-WmiObject -Namespace "Root\sms\Site_$SMS_SiteCode" -Class SMS_Program -ComputerName localhost -Filter "PackageID='$PackageID'"

        # Change for each Program the ProgramRunType to Whether or not a user is logged on
        foreach($item in $PackageQuery){

          Write-Log -severity 1 -component "Query Package" -Message "------ Package $($item.PackageName) has been queried."
          Write-Log -severity 1 -component "Processing Programs" -Message "-------- Program $($item.ProgramName) is ready to be changed."

            If(($item.ProgramName -like '*-Install') -or ($item.ProgramName -like '*-Remove')){

                # If Only when a user is logged on is enabled
                If($item.ProgramFlags -band ([math]::pow(2,14))){

                    # Changing Bit 0 to 1
                    $item.ProgramFlags = $item.ProgramFlags -bor ([math]::pow(2,13))
                    $item.put()
                    Write-Log -severity 1 -component "Changing Bit" -Message "--------- Bit Value Changed from 0 to 1 for: UNATTENDED. The program requires no user interaction."

                    # Changing Bit 1 to 0
                    $item.ProgramFlags = $item.ProgramFlags -bxor ([math]::pow(2,14))
                    $item.put()
                    Write-Log -severity 1 -component "Changing Bit" -Message "--------- Bit Value Changed from 1 to 0 for: USERCONTEXT. The program can run only when a user is logged on."

                    # Changing Bit 0 to 1
                    $item.ProgramFlags = $item.ProgramFlags -bor ([math]::pow(2,15))
                    $item.put()
                    Write-Log -severity 1 -component "Changing Bit" -Message "--------- Bit Value Changed from 0 to 1 for: ADMINRIGHTS. The program must be run as the local Administrator account."

                }
                Else{
                
                    Write-Log -severity 3 -component "Information" -Message "-------- Program $($item.ProgramName) is already configured to Whether or not a user is logged on." 
                
                }
            }
            Else{
                
                Write-Log -severity 3 -component "Information" -Message "-------- Program $($item.ProgramName) does not meet the requirements."
                
                }
        }

     Write-Log -severity 2 -component "End Processing Package" -Message "---- Finished to process package with PackageID: $PackageID."
}

I have noticed that due to a plugin, the view above might have some issues in showing the quotes properly. The script can be downloaded here.

I hope it helps and stay tuned.

Ken

1 Comment

  1. Anil KP

    Excellent Article Ken, very neatly explained. 🙂

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *