Table of Contents

Microsoft - Windows - Installer - Notes - Custom Actions

Returning status of VBScript in custom action

WScript.Quit does not work from a VBScript within an MSI package as Custom Action as the WScript object is not available.

Instead the script should contain a function that returns one of the return codes listed on Microsoft Docs - Windows Installer - Return Values of JScript and VBScript Custom Actions which will then get translated to an MSI return code as listed on Microsoft Docs - Windows Installer - Logging of Action Return Values.

The following is an example script that can be used in a Custom Action in an MSI to create a scheduled task on the system that runs at each users logon, and that returns it's success or error in the way that the MSI installer will succeed or fail based on the scripts result.

'CreateUserLogonScheduledTask.vbs
' For creating a scheduled task from MSI that runs at user logon of every user.
' 2022-06-30, v1.
' 2023-01-23, v2.
'   Added RemoveScheduledTask().
'   Moved the task name out of CreateScheduledTask() to the beginning of the script.

 
' Set the task name.
strTaskName = "UEM Office Branding"
 
Function CreateScheduledTask()
  Const bWaitOnReturn = True
  Const HideWindow = 0
 
  Set objShell = CreateObject ("Wscript.Shell") 
 
  strWinSystemDir = objShell.ExpandEnvironmentStrings("%SYSTEMROOT%") & "\System32\"
  strProgramFilesDir = objShell.ExpandEnvironmentStrings("%PROGRAMFILES%") & "\"
 
  ' Set the task properties.
  strWscriptExe = strWinSystemDir & "wscript.exe"
  strVBScript = strProgramFilesDir & "UEM Office Branding\RunPowerShellScript.vbs"
  strPowerShellScript = strProgramFilesDir & "UEM Office Branding\UEM_OfficeBranding.ps1"
 
  ' Check the OS install language to determine the correct Users group name to use.
  strInstallLanguage = objShell.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Nls\Language\Installlanguage")
  Select Case strInstallLanguage
    Case 0409 'English
      strGroup = "Users"
    Case 0413 'Dutch
      strGroup = "Gebruikers"
  End Select
 
  ' Create the scheduled task.
  intResult = objShell.Run(strWinSystemDir & "schtasks.exe /Create /TN " & Chr(34) & strTaskName & Chr(34) & " /TR " & Chr(34) & "\" & Chr(34) & strWscriptExe & "\" & Chr(34) & " " & "\" & Chr(34) & strVBScript & "\" & Chr(34) & " " & "\" & Chr(34) & strPowerShellScript & "\"  & Chr(34) & Chr(34) & " /SC ONLOGON /RU " & strGroup & " /F", HideWindow, bWaitOnReturn)
 
  ' Output proper return code for the MSI.
  if intResult = 0 Then
    CreateScheduledTask = 1
    Exit Function
  Else
    CreateScheduledTask = 3
    Exit Function
  End If
End Function
 
Function RemoveScheduledTask()
 
  Const bWaitOnReturn = True
  Const HideWindow = 0
 
  Set objShell = CreateObject ("Wscript.Shell") 
 
  strWinSystemDir = objShell.ExpandEnvironmentStrings("%SYSTEMROOT%") & "\System32\"
 
  intResult = objShell.Run(strWinSystemDir & "schtasks.exe /Delete " & _
    "/TN " & Chr(34) & strTaskName & Chr(34) & " " & _
    "/F", _
    HideWindow, bWaitOnReturn)
 
  If intResult <> 0 Then
    RemoveScheduledTask = 3
    Exit Function
  End If
 
End Function

This can then be used as a Custom Action in, for this example, Master Packager like so:

And for an uninstall:

And then the create and remove custom actions need to be added to the Install Execute Sequence in the MSI at the desired position in the installation process.

Example for Master Packager:

:?: Instead of using schtasks.exe I started with creating the scheduled task directly from VBScript based on Logon Trigger Example (Scripting), but I could not get the Principal.GroupId property to stick instead of LogonTrigger.UserId. :?:

Sources:

CreateUserLogonAndEvery5mScheduledTask.vbs

Same as the above example script, but now adds a second trigger to run the task every 5 minutes.

'CreateUserLogonAndEvery5mScheduledTask.vbs
' For creating a scheduled task from MSI that runs at user logon of
' every user and every 5m. 
' 2022-07-15, v1.
'   Based on CreateUserLogonScheduledTask.vbs v2.
' 2022-07-18, v2.
'   Commented out the WScript.Quit at the end.
' 2023-01-20, v3.
'   Added RemoveScheduledTask().
'   Moved the task name out of CreateScheduledTask() to the beginning of the script.

' Set the task name.
strTaskName = "UEM Enforce User Settings"
 
Function CreateScheduledTask()
  Const bWaitOnReturn = True
  Const HideWindow = 0
 
  Set objShell = CreateObject ("Wscript.Shell") 
 
  strWinSystemDir = objShell.ExpandEnvironmentStrings("%SYSTEMROOT%") & "\System32\"
  strProgramFilesDir = objShell.ExpandEnvironmentStrings("%PROGRAMFILES%") & "\"
 
  ' Set the task properties.
  strWscriptExe = strWinSystemDir & "wscript.exe"
  strVBScript = strProgramFilesDir & "UEM Enforce User Settings\RunPowerShellScript.vbs"
  strPowerShellScript = strProgramFilesDir & "UEM Enforce User Settings\UEM_EnforceUserSettings.ps1"
 
  ' Check the OS install language to determine the correct Users group name to use.
  strInstallLanguage = objShell.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Nls\Language\Installlanguage")
  Select Case strInstallLanguage
    Case 0409 'English (US)
      strGroup = "Users"
    Case 0809 'English (GB)
      strGroup = "Users"
    Case 0413 'Dutch
      strGroup = "Gebruikers"
  End Select
 
  ' Create the scheduled task.
  intResult = objShell.Run(strWinSystemDir & "schtasks.exe /Create " & _
    "/TN " & Chr(34) & strTaskName & Chr(34) & " " & _
    "/TR " & Chr(34) & "\" & Chr(34) & strWscriptExe & "\" & Chr(34) & " " & "\" & Chr(34) & strVBScript & "\" & Chr(34) & " " & "\" & Chr(34) & strPowerShellScript & "\"  & Chr(34) & Chr(34) & " " & _ 
    "/SC ONLOGON " & _
    "/RU " & strGroup & " " & _
    "/F", _
    HideWindow, bWaitOnReturn)
 
  ' Output proper return code for the MSI.
  If intResult = 0 Then
 
    ' Change the task to be able to run when the system is on batteries.
    intResult = objShell.Run(Chr(34) & strWinSystemDir & "WindowsPowerShell\v1.0\powershell.exe" & Chr(34) &  " -command " & Chr(34) & _
      "$objTaskSettings = (Get-ScheduledTask -TaskName \" & Chr(34) & strTaskName & "\" & Chr(34) & ").Settings; " & _ 
      "$objTaskSettings.DisallowStartIfOnBatteries = $false; " & _ 
      "Set-ScheduledTask -TaskName \" & Chr(34) & strTaskName & "\" & Chr(34) & " -Settings $objTaskSettings" & Chr(34), _
      HideWindow, bWaitOnReturn)
 
    If IntResult = 0 Then
 
      ' Add a scheduled task trigger to run every 5 minutes of every day.
      intResult = objShell.Run(Chr(34) & strWinSystemDir & "WindowsPowerShell\v1.0\powershell.exe" & Chr(34) &  " -command " & Chr(34) & _
        "$t1 = New-ScheduledTaskTrigger -Daily -At 00:05; " & _
        "$t2 = New-ScheduledTaskTrigger -Once -At 00:05 -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Hours 23 -Minutes 55); " & _
        "$t1.Repetition = $t2.Repetition; " & _
        "$trigger = @((Get-ScheduledTask -TaskName \"  & Chr(34) & strTaskName & "\" & Chr(34) & ").Triggers[0], $t1); " & _ 
        "Set-ScheduledTask -TaskName \"  & Chr(34) & strTaskName & "\" & Chr(34) & " -Trigger $trigger", _ 
        HideWindows, WaitOnReturn)
 
      If IntResult = 0 Then
        CreateScheduledTask = 1
        Exit Function
      End If
 
    End If
 
  End If
 
  If intResult <> 0 Then
    CreateScheduledTask = 3
    Exit Function
  End If
 
End Function
 
Function RemoveScheduledTask()
 
  Const bWaitOnReturn = True
  Const HideWindow = 0
 
  Set objShell = CreateObject ("Wscript.Shell") 
 
  strWinSystemDir = objShell.ExpandEnvironmentStrings("%SYSTEMROOT%") & "\System32\"
 
  intResult = objShell.Run(strWinSystemDir & "schtasks.exe /Delete " & _
    "/TN " & Chr(34) & strTaskName & Chr(34) & " " & _
    "/F", _
    HideWindow, bWaitOnReturn)
 
  If intResult <> 0 Then
    RemoveScheduledTask = 3
    Exit Function
  End If
 
End Function
 
'For testing outside of an MSI.
'WScript.Quit CreateScheduledTask

Sources: