Install all the MSI installation files in a folder

All the files in a folder

All the files in a folder

If you want to install a bunch of MSI files you put them in a folder and install them with a batch file. There is a downsize: you must modify the batch file for each situation. With this PowerShell script you can install all the MSI files in the folder, including applying transform and patch files. You can add your own properties in a csv settings file.

There are some parameters:

  • Install: Use this swith to specify an installation.
  • Uninstall: Use this swith to specify an uninstall.
  • MSIPath: Specify the location where the MSI files are located.
  • Loglocation: Specify the logfile location.

The default log location is C:\Windows\system32\LogFiles.

You can add a transform file.

You can add a transform file.

You can specify a settings file for each application.

You can specify a settings file for each application.

The default settings file is applied to all installations in the folder.

The default settings file is applied to all installations in the folder.

You can also use SCCM to install all the MSI files in the folder. Create an application with one deployment type. You can add all the product codes from each MSI file to identify a successful installation.

SCCM: The 'Application Catalog' tab.

SCCM: The ‘Application Catalog’ tab.

SCCM: Program

SCCM: Program

The Install line is:

The Uninstall line is:

SCCM: Detection rule

SCCM: Detection rule

Installation on the client.

Installation on the client.

Uninstall on the client.

Uninstall on the client.

A demonstration of this script can be found on my YouTube channel: the script and AppV Repository or view it below:

The script:

<#

.SYNOPSIS
    Installs all MSI's in a folder. By default, it is the folder where the script is located, but you can specify another location.
 
.DESCRIPTION
    Installs all MSI's in a folder. By default, it is the folder where the script is located, but you can specify another location.
    If there is a MST that starts with the MSI name, then the MSI / MST combination will be installed.   
    If there is a MSP that starts with the MSI name, then the MSI / MSP combination will be installed. 

    Valid combinations:
    -> MSI file: Application_1.20.msi
    -> MST file: Application_1.20_transform.mst
    -> MSP file: Application_1.20_Update_to_1.40.msp
    -> CSV file: Application_1.20.csv

    Invalid combinations:
    -> MSI file: Application_1.20.msi
    -> MST file: Application_1.20.msi.mst     (.msi should be removed)
    -> MST file: Application_patch.msp        (not the full msi file name)
 
.EXAMPLE
     Installs all the MSIs that are in the folder where the script is located.
     ."\install_all_msi (v10).ps1"

.EXAMPLE
     Installs all the MSIs that are in the folder where the script is located.
     ."\install_all_msi (v10).ps1" -Install

.EXAMPLE
     Uninstalls all the MSIs that are in the folder \\server\share\MSIs.
     ."\install_all_msi (v10).ps1" -Uninstall -MSIPath \\server\share\MSIs

.EXAMPLE
     Uninstalls all the MSIs that are in the folder \\server\share\MSIs. Logfiles are written to C:\Logs
     ."\install_all_msi (v10).ps1" -Uninstall -MSIPath \\server\share\MSIs -LogLocation C:\Logs

.NOTES
    Author:  Willem-Jan Vroom
    Website: https://www.vroom.cc/
    Twitter: @TheStingPilot

 v0.1:
   * Initial version.

 v1.0:
   * Added: properties handler:
         - defaultproperties.csv -> will be applied to all MSI packages or MSI / MST combination in the folder.
                                    This file can be in either the location where the script is, or in the location
                                    where the MSI files are. 
         - .csv     -> will be applied only to the given MSI or MSI / MST combination.
                                    This file must be in the same directory as the MSIs.
     This file must have the following header layout:
         Property,Value
         ALLUSERS,1
         ADDLOCAL,ALL
     The content may be different. 
   * Install and uninstall switch added.
   * Check if user has admin rights. It throws up an error in case not.
   * Added the command line options MSIPath and LogLocation.
   * Added patch support. The pathname must start with the same name as the MSI.
   * Check if both install and uninstall switches are used.
   * Bugfix: error messages when there are quotes around the MSI file name.

#>

[CmdLetBinding()]

param
 (
  # Use this switch to specify an installation.
  [Parameter(Mandatory=$False)]
  [Switch] $Install,

  # Use this switch to specify an uninstall.
  [Parameter(Mandatory=$False)]
  [Switch] $Uninstall,

  # Specify the location where the MSI files are located.
  [Parameter(Mandatory=$False)]
  [String] $MSIPath = "",

  # Specify the logfile location.
  [Parameter(Mandatory=$False)]
  [String] $LogLocation = ""
 )

# ========================================================================================================================
# Function block
# ========================================================================================================================

  Function CreateLogFile
  {

   <#
   .NOTES
   ========================================================================================================================
   Created with:     Windows PowerShell ISE
   Created on:       9-January-2019
   Created by:       Willem-Jan Vroom
   Organization:     
   Functionname:     CreateLogFile
   ========================================================================================================================
   .SYNOPSIS

   This function creates the logfile 

   #>

   param
    (
     [string] $LogFile
    )

   New-Item $LogFile -Force -ItemType File | Out-Null
  }
 
Function WriteToLog
 {
  <#
  .NOTES
  ========================================================================================================================
  Created with:     Windows PowerShell ISE
  Created on:       9-January-2019
  Created by:       Willem-Jan Vroom
  Organization:     
  Functionname:     WriteToLog
  ========================================================================================================================
  .SYNOPSIS

  This function adds a line to the logfile

  #>

  param
     (
      [string] $LogFile,
      [string] $line
     )

   $timeStamp = (Get-Date).ToString('G').Replace("/","-")
   $line = $timeStamp + " - " + $line
   Add-Content -Path $LogFile -Value $line -Force
 }

 Function Import-PropertyFile
  {
   <#
   .NOTES
   ========================================================================================================================
   Created with:     Windows PowerShell ISE
   Created on:       9-January-2019
   Created by:       Willem-Jan Vroom
   Organization:     
   Functionname:     Import-PropertyFile
   ========================================================================================================================
   .SYNOPSIS

   This function imports all the properties that are mentioned in the given property-file 

   #>

   param
    (
     [string] $PropertyFile
    )

   $arrItems      = @()
   $strProperties = ""

   if(Test-Path $PropertyFile)
    {
     $arrItems = @(Import-CSV $PropertyFile)

     if($arrItems.Count -ge 1)
      {
       WriteToLog -LogFile $strLogFile -line "The property file $PropertyFile is applied."
       ForEach($objItem in $arrItems)
        {
         $strProperty    = $objItem.Property
         $strValue       = $objItem.Value
         $strLine        = $strProperty + "=" + $strValue + " "
         $strProperties += $strLine 
        }
       }
    }
    
    Return $strProperties
  }

Function Get-MSIFileInformation
 {

  <#
  .NOTES
  ========================================================================================================================
  Created with:     Windows PowerShell ISE
  Created on:       9-January-2019
  Created by:       Willem-Jan Vroom
  Organization:     
  Functionname:     Get-MSIFileInformation
  ========================================================================================================================
  .SYNOPSIS

  This function reads the various properties from a MSI file. 
  This function has been found on http://www.scconfigmgr.com/2014/08/22/how-to-get-msi-file-information-with-powershell/
  All credits, including the copyright go to Nickolaj Andersen.

  #>


  param
   (
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [System.IO.FileInfo]$Path,
 
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateSet("ProductCode", "ProductVersion", "ProductName", "Manufacturer", "ProductLanguage", "FullVersion")]
    [string]$Property
   )
  
  Process 
   {
    try 
     {
        # Read property from MSI database
        $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
        $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0))
        $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"
        $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))
        $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
        $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
        $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)
         
        # Commit database and close view
        $MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)
        $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)           
        $MSIDatabase = $null
        $View = $null
 
        # Return the value
        return $Value
     } 
    catch 
     {
      Write-Warning -Message $_.Exception.Message ; break
     }
  }
  End 
  {
    # Run garbage collection and release ComObject
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null
    [System.GC]::Collect()
  }
}

Function Check-HasAdminRights
 {

  <#
  .NOTES
  ========================================================================================================================
  Created with:     Windows PowerShell ISE
  Created on:       11-January-2019
  Created by:       Willem-Jan Vroom
  Organization:     
  Functionname:     Check-HasAdminRights
  ========================================================================================================================
  .SYNOPSIS

  This function checks if an user has admin rights. The function returns $true or $false

  #>

  If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
   {
    Return $True
   }
    else
   {
    Return $False
   }
 }

 Function Remove-TrailingCharacter
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       18-January-2019
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Remove-TrailingCharacter
=============================================================================================================================================
.SYNOPSIS

This function removes a trailing character from a string
#>

param
(
 [string] $RemoveCharacterFrom = "",
 [string] $Character           = "" 
)

if($RemoveCharacterFrom.Length -gt 0)
 { 
  if(($RemoveCharacterFrom.SubString($RemoveCharacterFrom.Length-1,1)) -eq $Character) 
  {
   $RemoveCharacterFrom = $RemoveCharacterFrom.Substring(0,$RemoveCharacterFrom.Length-1)
  }
 }


 Return $RemoveCharacterFrom

}

 Function Add-TrailingCharacter
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Add-TrailingCharacter
=============================================================================================================================================
.SYNOPSIS

This function adds a trailing backslash to a string
#>

param
(
 [string] $AddCharacterTo = "",
 [string] $Character      = ""
)

if($AddCharacterTo.Length -gt 0)
 { 
  $AddCharacterTo = Remove-TrailingCharacter -RemoveCharacterFrom $AddCharacterTo -Character $([char]34)
  if(($AddCharacterTo.SubString($AddCharacterTo.Length-1,1)) -ne $Character) 
  {
   $AddCharacterTo = $AddCharacterTo + $Character
  }
 }
Else
 {
  $AddCharacterTo = $Character
 }

 Return $AddCharacterTo

}

Function Get-AllFilesWithPattern
 {

  <#
  .NOTES
  ========================================================================================================================
  Created with:     Windows PowerShell ISE
  Created on:       13-January-2019
  Created by:       Willem-Jan Vroom
  Organization:     
  Functionname:     Get-AllFilesWithPattern
  ========================================================================================================================
  .SYNOPSIS

  Find all files in the given folder that matches a filter.

  #>

  param
   (
    [string] $FolderToLookIn,
    [string] $Pattern
   )

   $FolderToLookIn = Remove-TrailingCharacter -Character "\" -RemoveCharacterFrom $FolderToLookIn

   $arrItems = @()
   $arrItems = Get-ChildItem -Path $FolderToLookIn -Filter $Pattern
   $arrItems | Sort-Object -Property Name | Out-Null

   Return $arrItems
 }

Function Get-LastItemOfAnArryAndPutItInAString
 {

  <#
  .NOTES
  ========================================================================================================================
  Created with:     Windows PowerShell ISE
  Created on:       13-January-2019
  Created by:       Willem-Jan Vroom
  Organization:     
  Functionname:     Get-LastItemOfAnArryAndPutItInAString
  ========================================================================================================================
  .SYNOPSIS

  Returns the last item of string.

  #>

  param
   (
    [string] $FileName,
    [string] $OldExtension,
    [string] $NewExtension,
    [string] $WhereToLook
   )

   $arrFiles    = @()
   $strFileName = ""
   $FilePattern = $FileName -Replace($OldExtension,$NewExtension)
   $arrFiles    = Get-AllFilesWithPattern -FolderToLookIn $WhereToLook -Pattern $FilePattern
   
   if($arrFiles.Count -gt 0)
    {
     $strFileName = $arrFiles[-1].ToString()
    }
 
   Return $strFileName
}

# ========================================================================================================================
# End function block
# ========================================================================================================================

# ========================================================================================================================
# Define the variables.
# ========================================================================================================================

  $strCurrentDir          = Split-Path -parent $MyInvocation.MyCommand.Definition

  if($MSIPath.Length -eq 0)
   {
    $MSIPath = $strCurrentDir
   }
  
  if($LogLocation.Length -eq 0)
   {
    $LogLocation = $Env:Windir + "\SYSTEM32\LogFiles"
   }
      
  $LogLocation            = Add-TrailingCharacter -AddCharacterTo $LogLocation   -Character "\"
  $MSIPath                = Add-TrailingCharacter -AddCharacterTo $MSIPath       -Character "\"
  $strCurrentDir          = Add-TrailingCharacter -AddCharacterTo $strCurrentDir -Character "\"

  $strTransform           = ""
  $strDefaultPropFile     = $MSIPath + "defaultproperties.csv"

  $strDefaultProperties   = ""
  $strActivity            = ""
  $strPatch               = ""
  $strSingleOrMultipleMSI = "MultipleMSI"

  $arrDefaultProperties   = @()
  $arrMSIFiles            = @()
  $arrMSPFiles            = @()
  $arrMSIFiles            = Get-AllFilesWithPattern -FolderToLookIn $MSIPath -Pattern "*.msi"
  $numMSIFiles            = $arrMSIFiles.Count
  $numCounter             = 1

# ========================================================================================================================
# Stop the script for a non admin user
# ========================================================================================================================

  if(-not(Check-HasAdminRights))
  {
   Write-Error "The current user has no admin rights. Please rerun the script with elevated rights." -Category PermissionDenied
   Exit 999
  }

# ========================================================================================================================
# Create the log file location if not exists
# ========================================================================================================================

  if(-not (Test-Path $LogLocation))
   {
    New-Item -Path $LogLocation -ItemType Directory -Force -Confirm:$False | Out-Null
   }

# ========================================================================================================================
# Define the logfile for the install or uninstall of all the MSIs. 
# ========================================================================================================================
 
  $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").log"
  $strLastPartOfFileName = $strLastPartOfFileName.Replace(":","-").Replace("/","-")

  if($numMSIFiles -eq 1)
   {
    $strSingleOrMultipleMSI = "SingleMSI"
   }

  if($Install -or (-not $Uninstall))
   {
    $strLogFile            = $LogLocation + $strSingleOrMultipleMSI + $strLastPartOfFileName
    $strActivity           = "Installing MSIs in the folder $MSIPath"
   }
    else
   {
    $strLogFile            = $LogLocation + "Uninstall" + $strSingleOrMultipleMSI + $strLastPartOfFileName
    $strActivity           = "Uninstalling MSIs in the folder $MSIPath"
   }
 
  CreateLogFile -LogFile $strLogFile

# ========================================================================================================================
# Give an error message if both parameters install and uninstall are used. 
# ========================================================================================================================

if($Install -and $Uninstall)
 {
  $strErrorMessage = "Both install and uninstall parameters are mentioned. That is not possible. Only one of them should be used."
  WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
  WriteToLog -line "FATAL ERROR!" -LogFile $strLogFile
  WriteToLog -line $strErrorMessage -LogFile $strLogFile
  WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
  Write-Error $strErrorMessage -Category InvalidArgument
  Exit 991
 }

# ========================================================================================================================
# Write default settings to the logfile
# ========================================================================================================================

  WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
  WriteToLog -line "Log location: $LogLocation" -LogFile $strLogFile
  WriteToLog -line "MSI Path:     $MSIPath"     -LogFile $strLogFile
  WriteToLog -line $($strActivity + ":")        -LogFile $strLogFile

  ForEach ($objMSIFile in $arrMSIFiles)
   {
    WriteToLog -line " * $($objMSIFile.Name)" -LogFile $strLogFile
   }
  WriteToLog -line "========================================================================================================================" -LogFile $strLogFile

# ========================================================================================================================
# In case of an installation:
# Define the default properties.
# ========================================================================================================================

  if ($Install -or (-not $Uninstall))
   {
    $strDefaultProperties = Import-PropertyFile -PropertyFile $strDefaultPropFile
   }
  
# ========================================================================================================================
# Start the real installation or uninstall.
# The installation is skipped if a MSI has already been installed.
# The uninstall is only done if the product has already been installed.
# ========================================================================================================================

  ForEach ($objMSIFile in $arrMSIFiles)
   {
    Write-Progress -Activity $($strActivity + ".") -Status "Processing $objMSIFile." -PercentComplete ($numCounter / $numMSIFiles * 100)
    $strMSIFileName    = $MSIPath + $objMSIFile
    $strProductName    = Get-MSIFileInformation -Path $strMSIFileName -Property ProductName
    $strProductVersion = Get-MSIFileInformation -Path $strMSIFileName -Property ProductVersion
    $strProductCode    = Get-MSIFileInformation -Path $strMSIFileName -Property ProductCode
    $strRegPathX64     = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"+$strProductCode
    $strRegPathX64     = $strRegPathX64 -replace(" ","")
    $strRegPathX86     = $strRegPathX64.Replace("WOW6432Node\","")
    
    if($Install -or (-not $Uninstall))
     {
           
      # ========================================================================================================================
      # The install
      # ========================================================================================================================

      $strTransform      = ""
      $strPatch          = ""
      $strMSTFileName    = Get-LastItemOfAnArryAndPutItInAString -FileName $objMSIFile -OldExtension ".msi" -NewExtension "*.mst" -WhereToLook $MSIPath
      $strMSPFileName    = Get-LastItemOfAnArryAndPutItInAString -FileName $objMSIFile -OldExtension ".msi" -NewExtension "*.msp" -WhereToLook $MSIPath
      $strPropFile       = $strMSIFileName.Replace("msi","csv")

      # ========================================================================================================================
      # Apply a patch (msp file) (if available)
      # ========================================================================================================================

      if($strMSPFileName.Length -ge 1)
       {
        $strPatch = " /update " + $([char]34) + $MSIPath + $strMSPFileName + $([char]34)
        WriteToLog -LogFile $strLogFile -line "The patch file '$strMSPFileName' has been found and is applied."
       }

      # ========================================================================================================================
      # Apply a transform file (mst) (if available)
      # ========================================================================================================================

      if($strMSTFileName.Length -ge 1)
       {
        $strMSTFileName = $MSIPath + $strMSTFileName
        $strTransform   = "TRANSFORMS=" + $([char]34) + $strMSTFileName + $([char]34)+" "
        WriteToLog -LogFile $strLogFile -line "The transform file '$strMSTFileName' has been found and is applied."
       }

      WriteToLog -line "Installing application: $strProductName"    -LogFile $strLogFile
      WriteToLog -line "ProductVersion:         $strProductVersion" -LogFile $strLogFile

      if(-not ((Test-Path $strRegPathX64) -or (Test-Path $strRegPathX86)))
       {
        $strProperties     = " "
        $strProperties     = Import-PropertyFile -PropertyFile $strPropFile
        $strMSILogFile     = "/l*v " + $([char]34) + $LogLocation + $strProductName + " " + $strProductVersion +".log" + $([char]34)
        $strArguments      = "/i "   + $([char]34) + $MSIPath + $objMSIFile + $([char]34) + $strPatch + " /qb! " + $strDefaultProperties + $strProperties + $strTransform + $strMSILogFile

        $strArguments      = $strArguments  -replace("   ","")
      
        WriteToLog -line "Command that is run:       msiexec $($strArguments)" -LogFile $strLogFile
        $StartProcess = (Start-Process -FilePath "msiexec.exe" -ArgumentList $strArguments -Wait -PassThru)
        WriteToLog -line "Result: $($StartProcess.ExitCode)" -LogFile $strLogFile
        WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
       }
        else
       {
        WriteToLog -line "This application has already been installed, thus skipping." -LogFile $strLogFile
        WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
       }
     }
      else
     {   
        
      # ========================================================================================================================
      # The uninstall
      # ========================================================================================================================

      WriteToLog -line "Uninstalling application: $strProductName"    -LogFile $strLogFile
      WriteToLog -line "ProductVersion:           $strProductVersion" -LogFile $strLogFile
      
      if((Test-Path $strRegPathX64) -or (Test-Path $strRegPathX86))
       {
        $strMSILogFile     = "/l*v " + $([char]34) + $LogLocation + "Uninstall_"+ $strProductName + " " + $strProductVersion +".log" + $([char]34)
        $strArguments      = "/x "   + $strProductCode + " /qb! " + $strMSILogFile

        $strArguments      = $strArguments  -replace("   ","")

        WriteToLog -line "Command that is run:         msiexec $($strArguments)" -LogFile $strLogFile
        $StartProcess = (Start-Process -FilePath "msiexec.exe" -ArgumentList $strArguments -Wait -PassThru)
        WriteToLog -line "Result: $($StartProcess.ExitCode)" -LogFile $strLogFile

        WriteToLog -line "========================================================================================================================" -LogFile $strLogFile
       }
        else
       {
        WriteToLog -line "This application has not been installed, thus skipping." -LogFile $strLogFile
        WriteToLog -line " " -LogFile $strLogFile
       }
     }
    $numCounter++
   }

# ========================================================================================================================
# Done!
# ========================================================================================================================

Current version: install_all_msi (v10).zip




Cannot edit the object which is in use as SCCM blocks an application

You might have had this earlier: editing an application in SCCM 2012, the SCCM console crashes. And when you want to continue, you get the error: The following objects are not available and wil not be edited.

In this article, I will describe 2 solutions.

Via PowerShell

  1. The easiest way is via PowerShell. In the SCCM Console start PowerShell:

  2. Give the following command:
    Unlock-CMObject -InputObject $(Get-CMApplication -Name Microsoft_CPlusPlusRuntimes_1.0.1_ENG_WIN7_P1)
    


    There is no feedback, but you can edit the application again.

Via SQL Server Management Studio

An alternative way is via SQL Server Management Studio.

  1. Start SQL Server Management Studio
  2. Create a new query. And connect to the correct database.
  3. Give the following command:
    select * from SEDO_LockState where LockStateID<>0
    

  4. There might be multiple lines, as multiple users may have various applications locked. Find the correct one. Copy the LockstateID.
  5. Give the following command:
    DELETE from SEDO_LockState where LockID = 'Copied LockstateID'
    

  6. And you can edit your application again.

 




Installing OneDrive NextGen Synchronisation Client

The OneDrive for Business client can give some difficulties, like unable to synchronize:

Unable to sync files with OneDrive for Business.

Unable to sync files with OneDrive for Business.

This can be solved by installing the new OneDrive Next Gen Sync Client. In this article I will describe how this application can be installed via SCCM 2012.

Please be informed that you still need the OneDrive for Business client to synchronize with Sharepoint lists.

Background information

The OneDrive NextGen Sync Client is installed the users’ %LOCALAPPDATA% directory. As the application is installed the %LOCALAPPDATA% directory, you can assume that you can install the application with user rights in SCCM 2012. If you do so, the installation will fail:

Failure as admin rights are needed to install.

Failure as admin rights are needed to install.


As you can see, there are ‘hidden’ admin rights needed.

The solution

  • Created a vscript that installs the application and sets a detection rule after a successful installation. The detection rule can be used in SCCM 2012 to detect a successful installation.
    ' ================================================================================================
    ' Installs Microsoft OneDriveNextGenSyncClient 17.3.6386.0412
    ' Created by Willem-Jan Vroom
    ' Version history:
    '
    ' 0.0.1
    '    Initial version
    '
    ' 1.0.0
    '    Final version
    '
    ' ================================================================================================
    
    ' ------------------------------------------------------------------------------------------------
    ' Declare the most variables.
    ' ------------------------------------------------------------------------------------------------
    
      Option Explicit
    
      Dim objShell                          : set objShell                      = WScript.CreateObject("WScript.Shell")
      Dim objLogFileFSO                     : Set objLogFileFSO                 = CreateObject("Scripting.FileSystemObject")
      Dim objFSO                            : Set objFSO                        = CreateObject("Scripting.FileSystemObject")
      Dim objProcessEnv                     : Set objProcessEnv                 = objShell.Environment("PROCESS")
      Dim objWMIService                     : Set objWMIService                 = GetObject("winmgmts:\\.\root\cimv2")
      Dim fn_objWMIService                  : Set fn_objWMIService              = GetObject("winmgmts:\\.\root\cimv2")
      Dim objReg                            : Set objReg                        = GetObject("winmgmts:\\.\root\default:StdRegProv")
      Dim objLogFile
      Dim CurrentDir                        : CurrentDir                        = Left(Wscript.ScriptFullname, InstrRev(Wscript.ScriptFullname, "\"))
                                              CurrentDir                        = Left(CurrentDir,len(CurrentDir)-1)
      Dim strcomputerName                   : strcomputerName                   = objProcessEnv("COMPUTERNAME")
      Dim strLogLocation                    : strLogLocation                    = "C:\WINDOWS\system32\Logfiles"
      Dim strOutputFile                     : strOutputFile                     = strLogLocation & "\" & strcomputerName & "_Installation_OneDriveSyncClient_" & Replace(FormatDateTime(Now(), 2),"/","-") & ".log"
      Dim strArchitecture                   : strArchitecture                   = "x86"
      Dim strCurrentUser                    : strCurrentUser                    = ""
      Dim strCurrentUserSID                 : strCurrentUserSID                 = ""
      Dim strOS                             : strOS                             = ""
      Dim strCommand                        : strCommand                        = ""
      Dim strLine                           : strLine                           = ""
      Dim strQuery                          : strQuery                          = ""
      Dim strCommonDesktop                  : strCommonDesktop                  = ""
      Dim strArray                          : strArray                          = ""
      Dim strProcess                        : strProcess                        = ""
      Dim strLanguage                       : strLanguage                       = ""
      Dim strInstallLanguage                : strInstallLanguage                = ""
      Dim strDetectUserUILanguage           : strDetectUserUILanguage           = ""
      Dim strValue                          : strValue                          = ""
      Dim strKeyPath                        : strKeyPath                        = ""
      Dim strValueName                      : strValueName                      = ""
      Dim strCommonPrograms                 : strCommonPrograms                 = ""
      Dim strFileToDelete                   : strFileToDelete                   = ""
      dim strProgramFilesFolder             : strProgramFilesFolder             = objProcessEnv("ProgramFiles")
      Dim strDotNetInstallPath              : strDotNetInstallPath              = ""
      Dim strWindir                         : strWinDir                         = objShell.ExpandEnvironmentStrings("%WinDir%")
      Dim dwValue                           : dwValue                           = 0
    
      Dim arrFileNamesToKill                : arrFileNamesToKill                = Array("doesnotexist.exe")
      Dim objFileNameToKill
    
      Dim arrArguments
      Dim arrLanguage
      Dim strDNSDomain                      : strDNSDomain                      = CreateObject("ADSystemInfo").DomainShortName
      Dim valCounter
      Dim valResult
      Dim valReturnCode
    
      Dim colProcess, objProcess
      Dim valOSBuildNumber
      Dim objSubfolder
      Dim colItems, objItem, Subfolder
      Dim colSoftware, objSoftware
      Dim arrValues, arrSubKeys
    
      Const ForWriting           = 2
      Const ForReading           = 1
      Const ForAppending         = 8
      Const OverWriteFiles       = True
      const HKEY_CURRENT_USER    = &H80000001
      const HKEY_LOCAL_MACHINE   = &H80000002
      Const HKEY_USERS           = &H80000003
    
      Const DEBUGMODE            = False
    
    ' ------------------------------------------------------------------------------------------------
    ' Create the log location (if not exists)
    ' Open the logfile.
    ' ------------------------------------------------------------------------------------------------
    
      CreateFolderStructure(strLogLocation)
      OpenLogFile()
      WriteToLog "- ACTION: script started."
      WriteToLog " "
    
    ' ------------------------------------------------------------------------------------------------
    ' Detect the current OS.
    ' ------------------------------------------------------------------------------------------------
    
      Set colItems = objWMIService.ExecQuery("Select Caption,BuildNumber from Win32_OperatingSystem")
      For Each objItem in colItems
          strOS            = objItem.Caption
          valOSBuildNumber = objItem.BuildNumber
      next
    
    ' ------------------------------------------------------------------------------------------------
    ' Find the currently logged on userid.
    ' And translate it to the users SID.
    ' ------------------------------------------------------------------------------------------------
    
      Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process where Name = 'explorer.exe'")
      For Each objProcess in colProcess
          valReturnCode = objProcess.GetOwner(strCurrentUser)
          if valReturnCode <> 0 Then strCurrentUser = "Unable to find the username!"
      Next
    
      strCurrentUser = Ucase(strDNSDomain) & "\" & strCurrentUser
    
      strCurrentUserSID = GetSIDFromUser(strCurrentUser)
    
    ' ------------------------------------------------------------------------------------------------
    ' Detect the current processor architecture.
    ' ------------------------------------------------------------------------------------------------
    
      if objFSO.FolderExists(strWindir & "\syswow64") Then
         strArchitecture = "x64"
         strProgramFilesFolder = objProcessEnv("ProgramFiles(x86)")
      end if
    
    ' ------------------------------------------------------------------------------------------------
    ' Detect the current User Interface MUI.
    ' ------------------------------------------------------------------------------------------------
    
      strDetectUserUILanguage = DetectUserUILanguage
    
      WriteToLog("########## Details regarding operating system and logged on user     ##########")
      WriteToLog("Found Operating System:       " & strOS)
      WriteToLog("Found architecture:           " & strArchitecture)
      WriteToLog("Logged on userid:             " & strCurrentUser)
      WriteToLog("Logged on user SID:           " & strCurrentUserSID)
      WriteToLog("Language:                     " & strDetectUserUILanguage)
      WriteToLog("########## End details regarding operating system and logged on user ##########")
      WriteToLog(" ")
      WriteToLog("########## Installation                                              ##########")
    
      ' ------------------------------------------------------------------------------------------------
      ' Close applications that might screw up the installation.
      ' ------------------------------------------------------------------------------------------------
    
      For Each objFileNameToKill in arrFileNamesToKill
        fnKillProcess(objFileNameToKill)
      Next
    
    ' ------------------------------------------------------------------------------------------------
    ' Sets SEE_MASK_NOZONECHECKS to 1.
    ' That will avoid a screen that asks for permission to run an application from
    ' a share or mapped drive.
    ' ------------------------------------------------------------------------------------------------
    
      objProcessEnv("SEE_MASK_NOZONECHECKS") = 1
    
    ' ------------------------------------------------------------------------------------------------
    ' Uninstall other Samsung Kies installations.
    ' if the command line option '/uninstall' is given, then only a uninstall and quit.
    ' ------------------------------------------------------------------------------------------------
    
      if wscript.arguments.count > 0 Then
         if lcase(wscript.arguments(0)) = "/uninstall" or lcase(wscript.arguments(0)) = "/remove" then
    	WriteToLog("The command line option '" & wscript.arguments(0) & "' was found, so only a silent uninstall will be performed.")
            RemoveOneDriveSyncClient()
            WriteToLog("########## End Uninstall                                      ##########")
            WriteToLog(" ")
            CloseLogFile()
            wscript.quit
         end if
      end if
    
    ' ------------------------------------------------------------------------------------------------
    ' Start the base installation
    ' ------------------------------------------------------------------------------------------------
    
      strCommand = chr(34) & CurrentDir & "\OneDriveSetup.exe" & chr(34)
      strCommand = strCommand & " /silent"
      WriteToLog ("Running command: " & strCommand)
      valResult = objShell.Run (strCommand,6,True)
      WriteToLog("Result: " & valResult)
      if valResult = 0 Then 
         GenereteSCCM2012DetectionRule
      end if
      WriteToLog("########## End Installation                                          ##########")
      WriteToLog(" ")
      CloseLogFile()
      wscript.quit valReturnCode
    
    Sub OpenLogFile()
    
    ' ------------------------------------------------------------------------------------------------
    ' Subroutine: OpenLogFile()
    ' The name of the logfile is mentinoed in the variabele strOutputFile.
    ' ------------------------------------------------------------------------------------------------
    
      If objLogFileFSO.FileExists(strOutputFile) Then
         Set objLogFile = objLogFileFSO.OpenTextFile(strOutputFile, ForAppending)
             Else
         Set objLogFile = objLogFileFSO.CreateTextFile(strOutputFile)
      End If
    
    End Sub
    
    Sub CloseLogFile()
    
    ' ------------------------------------------------------------------------------------------------
    ' Subroutine: CloseLogFile()
    ' Close the log file.
    ' ------------------------------------------------------------------------------------------------
    
      WriteToLog "- ACTION: script ended."
      objLogFile.Close
      Set objLogfileFSO = Nothing
    
    End Sub
    
    Function WriteToLog(sLogMessage)
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: WriteToLog(sLogMessage)
    ' Writes an entry 'sLogMessage' in the logfile.
    ' ------------------------------------------------------------------------------------------------
    
      if instr(sLogMessage, "- ACTION: ") = 0 then sLogMessage = "          " & sLogMessage
      objLogFile.WriteLine("Time: " & now & "  " & sLogMessage)
    
    End Function
    
    Function GetSIDFromUser(UserName)
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: GetSIDFromUser(UserName)
    ' Gets the SID from the give username.
    ' We use the registry to avoid an empty result if run under the SYSTEM account.
    ' ------------------------------------------------------------------------------------------------
    
      Dim DomainName, Result, WMIUser
    
      If InStr(UserName, "\") > 0 Then
        DomainName = Mid(UserName, 1, InStr(UserName, "\") - 1)
        UserName = Mid(UserName, InStr(UserName, "\") + 1)
      Else
        DomainName = CreateObject("WScript.Network").UserDomain
      End If
    
      On Error Resume Next
      Set WMIUser = GetObject("winmgmts:{impersonationlevel=impersonate}!" _
        & "/root/cimv2:Win32_UserAccount.Domain='" & DomainName & "'" _
        & ",Name='" & UserName & "'")
      If Err.Number = 0 Then
        Result = WMIUser.SID
      Else
        Result = ""
      End If
      On Error GoTo 0
    
      GetSIDFromUser = Result
    
    End Function
    
    Sub CreateFolderStructure(strFolderNameToBeCreated)
    
    ' ------------------------------------------------------------------------------------------------
    ' Subroutine: CreateFolderStructure(strFolderNameToBeCreated)
    ' Creates the map as mentioned in strFolderNameToBeCreated.
    ' ------------------------------------------------------------------------------------------------
    
      Dim arrFoldersTMP : arrFoldersTMP = split (strFolderNameToBeCreated,"\")
      Dim strFolder  : strFolder  = ""
      Dim objFolderTMP
    
      For Each objFolderTMP in arrFoldersTMP
          strFolder = strFolder & objFolderTMP
          If NOT objFSO.FolderExists(strFolder) Then
                 objFSO.CreateFolder(strFolder)
          end If
          strFolder = strFolder & "\"
      Next
    
    End Sub
    
    Function DetectUserUILanguage
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: DetectUserUILanguage
    ' Detects the User Interface Language 
    ' Modified by Willem-Jan Vroom d.d 12 Feb 14: also detects Czech, Finnish, Hungarian, Polish,
    ' Russian, Slovak, Slovian and Turkish languages on Windows 7.
    ' ------------------------------------------------------------------------------------------------
      
      if instr(lcase(strOS),"windows xp") > 0 then
         strKeyPath               = strCurrentUserSID & "\Control Panel\Desktop"
         strValueName             = "MultiUILanguageId"
         strLanguage              = "eng"
         objReg.GetStringvalue HKEY_USERS,strKeyPath,strValueName,strValue
         if NOT IsEmpty(strValue) Then
            strValue = UCase(right(strValue,4))
            if strValue = "040C" then strLanguage = "fra"
            if strValue = "0410" then strLanguage = "ita"
            if strValue = "0407" then strLanguage = "ger"
            if strValue = "040A" then strLanguage = "spa"
            if strValue = "0C0A" then strLanguage = "spa"
         end if
          else
         strKeyPath               = strCurrentUserSID & "\Control Panel\Desktop"
         strValueName             = "PreferredUILanguages"
         strLanguage              = "eng"
         objReg.GetMultiStringValue HKEY_USERS,strKeyPath,strValueName,arrValues
         if not IsNull(arrValues) Then
            strValue=arrValues(0)
            if NOT IsEmpty(strValue) then
               strValue = lcase(left(strValue,2))
               if strValue = "fr" then strLanguage = "fra"
               if strValue = "it" then strLanguage = "ita"
               if strValue = "de" then strLanguage = "ger"
               if strValue = "es" then strLanguage = "spa"
               if strValue = "cs" then strLanguage = "cze"
               if strValue = "fi" then strLanguage = "fin"
               if strValue = "hu" then strLanguage = "hun"
               if strValue = "pl" then strLanguage = "pol"
               if strValue = "ru" then strLanguage = "rus"
               if strValue = "sk" then strLanguage = "sky"
               if strValue = "sl" then strLanguage = "slv"
               if strValue = "sv" then strLanguage = "swe"
               if strValue = "tr" then strLanguage = "tur"
               if strValue = "nb" then strLanguage = "nor"
            end if
         end if
         if strLanguage = "eng" then
            strKeyPath               = ".DEFAULT\Control Panel\Desktop\MuiCached"
            strValueName             = "MachinePreferredUILanguages"
            objReg.GetMultiStringValue HKEY_USERS,strKeyPath,strValueName,arrValues
            if not IsNull(arrValues) Then
               strValue=arrValues(0)
               if NOT IsEmpty(strValue) then
                  strValue = lcase(left(strValue,2))
                  if strValue = "fr" then strLanguage = "fra"
                  if strValue = "it" then strLanguage = "ita"
                  if strValue = "de" then strLanguage = "ger"
                  if strValue = "es" then strLanguage = "spa"
                  if strValue = "cs" then strLanguage = "cze"
                  if strValue = "fi" then strLanguage = "fin"
                  if strValue = "hu" then strLanguage = "hun"
                  if strValue = "pl" then strLanguage = "pol"
                  if strValue = "ru" then strLanguage = "rus"
                  if strValue = "sk" then strLanguage = "sky"
                  if strValue = "sl" then strLanguage = "slv"
                  if strValue = "sv" then strLanguage = "swe"
                  if strValue = "tr" then strLanguage = "tur"
                  if strValue = "nb" then strLanguage = "nor"
               end if
            end if
         end if     
      end if
    
      DetectUserUILanguage = strLanguage
    End Function
    
    Function fnKillProcess(strProcessName)
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: fnKillProcess(strProcessName)
    ' Terminates the given processname.
    ' ------------------------------------------------------------------------------------------------
    
      Set colProcess = fn_objWMIService.ExecQuery ("Select * From Win32_Process")
      For Each objProcess In colProcess
        If Instr(LCase(objProcess.Name),LCase(strProcessName)) > 0 Then
           objShell.Run "TASKKILL /F /T /IM " & objProcess.Name, 0, False
           objProcess.Terminate()
           WriteToLog("Terminating application: " & objProcess.Name)
        End If
      Next
    
    End Function
    
    Function GenereteSCCM2012DetectionRule
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: GenereteSCCM2012DetectionRule
    ' Creates the SCCM 2012 Detection Rule to detect a successfull installation.
    ' ------------------------------------------------------------------------------------------------
    
      WriteToLog ("Creates the SCCM 2012 Detection Rule.")
      strKeyPath   = "SOFTWARE\VroomSoft\SCCM2012DetectionRules"
      strValueName = "Install_OneDriveSyncClient_17.3.6386.0412"
      strValue     = "true"
      objReg.CreateKey      HKEY_LOCAL_MACHINE, strKeyPath  
      objReg.SetStringValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName, strValue
      
    End Function
    
    Function RemoveOneDriveSyncClient()
    
    ' ------------------------------------------------------------------------------------------------
    ' Function: RemoveOneDriveSyncClient()
    ' Removes OneDrive Next Gen Sync Client.
    ' ------------------------------------------------------------------------------------------------
    
      strCommand = chr(34) & CurrentDir & "\OneDriveSetup.exe" & chr(34)
      strCommand = strCommand & " /uninstall"
      WriteToLog ("Running command: " & strCommand)
      valResult = objShell.Run (strCommand,6,True)
      WriteToLog("Result: " & valResult)
      if valResult = 0 Then
         strKeyPath   = "SOFTWARE\VroomSoft\SCCM2012DetectionRules"
         strValueName = "Install_OneDriveSyncClient_17.3.6386.0412"
         objReg.DeleteValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName
      end if 
      
    End Function
    
  • This is the detection rule that will be used in SCCM 2012:
    Detection rule set in the vbscript.

    Detection rule set in the vbscript.

  • Implementation in SCCM 2012

    1. Create a ‘installation program’ and ‘uninstall program’:
      New application.

      New application.

    2. The detection rule:
      Detection rule.

      Detection rule.

    3. The detection method:
      Detection method.

      Detection method.

    4. The application is installed with admin rights:
      Installation with admin rights.

      Installation with admin rights.

    5. And deploy the application to a deviced based collection:
      Deployment to a device based collection.

      Deployment to a device based collection.


      Deploy as available.

      Deploy as available.


      Show all notifications.

      Show all notifications.

    Check on a client

    1. Log on to a client and go to the Software Catalog:
      The application is visible in the software center

      The application is visible in the software center


      You can find the new application under installed applications.

      You can find the new application under installed applications.

    2. You can use regedit to find the detection rule which is set via vbscript:
      The detection rule is found in the registry

      The detection rule is found in the registry

    3. The installation log file (found in c:\windows\system32\logfiles):
      Installation log file

      Installation log file

    4. Now you can select the folders you want to synchronize or want to exclude from synchronization:
      You can select the folders you want so synchronize.

      You can select the folders you want so synchronize.


      Unselect the folders you do not want to synchronize.

      Unselect the folders you do not want to synchronize.

    Uninstall the application

    You can uninstall the OneDrive NextGen Sync Client if you do not want to use it anymore. All the data will remain on the computer:

    You can uninstall the client.

    You can uninstall the client.


    The uninstall in the log file.

    The uninstall in the log file.




Upgrading and deploying AppV 5.0 client via SCCM 2012

If you want to deploy AppV 5.0 packages, a client is needed. In this article I will describe all the steps that are needed to install the latest AppV 5.0 client. When writing this article it is AppV 5.0 SP3 with hotfix 2.

This version is not available as a ‘complete’ version. First you will have to install AppV 5.0 SP3 client, followed by the hotfix. Fortunately you can ‘merge’ this 2 versions in one installer.

The technique

If you extract the AppV 5.0 SP3 installer, you will find 2 MSI files: one for 86 bits and one for 64 bits operating systems. These versions will be patched with the hotfix.

Step 1:: download the software

Download the AppV 5.0 SP3 (MDOP 2014 R2)

You can download MDOP 2014 R2 – which includes AppV 5.0 SP3 – from the Microsoft Volume Licensing Service Center.

Download AppV 5.0 SP3 hotfix 2

You can download Hotfix Package 2 for Microsoft Application Virtualization 5.0 Service Pack 3.
Once completed all the required steps, save the file 484280_intl_x64_zip.exe in the folder C:\AppV50SP3.

Step 2: extract the software

Extract the main installer

  1. Create the folder C:\AppV50SP3
  2. Copy appv_client_setup.exe to C:\AppV50SP3
  3. Extract the content via the command:
    C:\AppV50SP3\appv_client_setup.exe /layout C:\AppV50SP3
    
  4. There are 2 additionals MSI files in C:\AppV50SP3: for each platform one MSI installer.

Extract the hotfix 2

  1. Dubbleclick on C:\AppV50SP3\484280_intl_x64_zip.exe and extract to C:\AppV50SP3

    Extract the hotfix.

    Extract the hotfix.

  2. Extract the patches with the command:
    C:\AppV50SP3\AppV5.0SP3_Client_KB3060458.exe /layout C:\AppV50SP3
    

Create the patched MSI file for both x86 and x64 operating systems

This is done via an old technique: create an administrative installation point and patch that administrative installation point with the patch.

  1. Create an administrative installation point for the x86 bits client:
    msiexec /a "C:\AppV50SP3\appv_client_MSI_x86.msi" TARGETDIR=c:\AppV50SP3HF2_aip_x86
    

    The MSI (x86) after administrative install

    The MSI (x86) after administrative install.

  2. Create an administrative installation point for the x64 bits client:
    msiexec /a "C:\AppV50SP3\appv_client_MSI_x64.msi" TARGETDIR=c:\AppV50SP3HF2_aip_x64
    

    The MSI (x64) after administrative install.

    The MSI (x64) after administrative install.

  3. Patch the msi in the administrative installation point with the patch for the x86 bits client:
    msiexec /a "C:\AppV50SP3HF2_aip_x86\appv_client_MSI_x86.msi" /p "C:\AppV50SP3\appv_client_kb3060458_x86.msp"
    

    The MSI (x86) after patching the administrative install.

    The MSI (x86) after patching the administrative install.

  4. Patch the msi in the administrative installation point with the patch for the x64 bits client:
    msiexec /a "C:\AppV50SP3HF2_aip_x64\appv_client_MSI_x64.msi" /p "C:\AppV50SP3\appv_client_kb3060458_x64.msp"
    

    The MSI (x64) after patching the administrative install.

    The MSI (x64) after patching the administrative install.

  5. An overview of the commands:

    Summary of all the commands.

    Summary of all the commands.

Step 3: implement in SCCM 2012

Place the installation files on the software library

  1. Copy the content from C:\AppV50SP3HF2_aip_x86 to the share with all the packages: \\demo-sccm\SCCMPackages\All Applications\Microsoft_AppVClient_5.0SP3HF2_ENG_W7_1.0.0\x86

    Copy the installation files (x86) to the software library.

    Copy the installation files (x86) to the software library.

  2. Copy the content from C:\AppV50SP3HF2_aip_x64 to the share with all the packages: \\demo-sccm\SCCMPackages\All Applications\Microsoft_AppVClient_5.0SP3HF2_ENG_W7_1.0.0\x64

    Copy the installation files (x64) to the software library.

    Copy the installation files (x64) to the software library.

Create the software package

Create the SCCM 2012 applications as per screen prints below. The installation command line should at least contain ACCEPTEULA=1. I always add ENABLEPACKAGESCRIPTS=1 so you can add virtualized Click 2 Run packages later.Then there is no need to set this via GPO later.

Upg After creating the application and deployment types.

Upg After creating the application and deployment types.

Installation program (x86).

Installation program (x86).

Detection rule (x86)

Detection rule (x86)

Requirements (x86).

Requirements (x86).

Dependencies (x86).

Dependencies (x86).

Installation program (x64).

Installation program (x64).

Detection rule (x64).

Detection rule (x64).

Requirements (x64).

Requirements (x64).

Dependencies (x64).

Dependencies (x64).

Deployment of the application.

Deployment of the application.

The deployment collection.

The deployment collection.

The deployment is set as required.

The deployment is set as required.

Ignore the maintenance window for installations.

Ignore the maintenance window for installations.

Confirmation.

Confirmation.

Deploy the software and check on the client

Force the computers in the collection to download the computer policies.

Force the computers in the collection to download the computer policies.

The policy is applied on the client.

The policy is applied on the client.

And the new AppV client is installed.

And the new AppV client is installed.

The new client version.

The new client version.

The deep dive: more info about the upgrade

For whatever reason, Microsoft has decided not to change the ProductCode property. Thus all the AppV 5.0 SP3 packages share the same ProductCode.

If you open C:\AppV50SP3\appv_client_MSI_x86.msi and apply C:\AppV50SP3\appv_client_kb3060458_x86.msp you will see that only the ProductVersion is updated:

The patch does only update the ProductVersion (x86).

The patch does only update the ProductVersion (x86).

Although:

The PATCHNEWPACKAGECODE property is ignored (x86).

The PATCHNEWPACKAGECODE property is ignored (x86).

So if you want to update the AppV50SP3 client to AppV50SP3 with HF2 then you can do 2 thinks:

  • Uninstall the old AppV50SP3 client and install the new AppV50SP3HF2 client.
  • Apply only the patch. I have not tested this scenario.



Load all AppV 5.0 packages into the AppV Cache

After the migration from SCCM 2007 to SCCM 2012 some users had an issue with accessing their AppV 5.0 packages: the virtual package could not be found in the ccmcache. That was true, as the package had not been used before the SCCM 2007 to SCCM 2012 migration and after the migration the ccmcache was deleted. So the only solution was to mount all AppV 5.0 packages. That will download the virtual package from the ccmcache into the AppV 5.0 cache.

To do this, I created a vbscript that is run just before the SCCM 2007 to SCCM 2012 migration task sequence.

' ================================================================================================
' Mount all AppV packages
' Created by Willem-Jan Vroom
' Version history:
'
' 0.0.1
'    Initial version
'
' 
' ================================================================================================

' ------------------------------------------------------------------------------------------------
' Declare the most variables. 
' ------------------------------------------------------------------------------------------------

  Option Explicit

  Dim objShell                          : set objShell                      = WScript.CreateObject("WScript.Shell")
  Dim objLogFileFSO                     : Set objLogFileFSO                 = CreateObject("Scripting.FileSystemObject")
  Dim objFSO                            : Set objFSO                        = CreateObject("Scripting.FileSystemObject")
  Dim objProcessEnv                     : Set objProcessEnv                 = objShell.Environment("PROCESS")
  Dim objWMIService                     : Set objWMIService                 = GetObject("winmgmts:\\.\root\cimv2")
  Dim fn_objWMIService                  : Set fn_objWMIService              = GetObject("winmgmts:\\.\root\cimv2")
  Dim objReg                            : Set objReg                        = GetObject("winmgmts:\\.\root\default:StdRegProv")
  Dim objLogFile
  Dim CurrentDir                        : CurrentDir                        = Left(Wscript.ScriptFullname, InstrRev(Wscript.ScriptFullname, "\"))
                                          CurrentDir                        = Left(CurrentDir,len(CurrentDir)-1)
  Dim strcomputerName                   : strcomputerName                   = objProcessEnv("COMPUTERNAME")
  Dim strLogLocation                    : strLogLocation                    = "C:\WINDOWS\system32\Logfiles"
  Dim strOutputFile                     : strOutputFile                     = strLogLocation & "\" & strcomputerName & "_Mount_All_AppV_Packages_" & Replace(FormatDateTime(Now(), 2),"/","-") & ".log"
  Dim strArchitecture                   : strArchitecture                   = ""
  Dim strCurrentUser                    : strCurrentUser                    = ""
  Dim strCurrentUserSID                 : strCurrentUserSID                 = "" 
  Dim strOS                             : strOS                             = ""
  Dim strCommand                        : strCommand                        = ""
  Dim strLine                           : strLine                           = ""
  Dim strQuery                          : strQuery                          = ""
  Dim strCommonDesktop                  : strCommonDesktop                  = ""
  Dim strArray                          : strArray                          = ""  
  Dim strAppDataFolder                  : strAppDataFolder                  = ""
  Dim strProcess                        : strProcess                        = ""
  Dim strLanguage                       : strLanguage                       = ""
  Dim strInstallLanguage                : strInstallLanguage                = ""
  Dim strDetectUserUILanguage           : strDetectUserUILanguage           = ""
  Dim strValue                          : strValue                          = ""
  Dim strKeyPath                        : strKeyPath                        = ""
  Dim strValueName                      : strValueName                      = ""
  Dim dwValue                           : dwValue                           = 0

  Dim arrFileNamesToKill                : arrFileNamesToKill                = Array("doesnotexists.exe")
  Dim objFileNameToKill

  Dim arrArguments
  Dim arrLanguage
  Dim strDNSDomain                      : strDNSDomain                      = CreateObject("ADSystemInfo").DomainShortName
  Dim valCounter
  Dim valResult 
  Dim valReturnCode

  Dim colProcess, objProcess 
  Dim valOSBuildNumber
  Dim objSubfolder
  Dim colItems, objItem, Subfolder
  Dim colSoftware, objSoftware
  Dim arrValues


  Const ForWriting           = 2
  Const ForReading           = 1
  Const ForAppending         = 8
  Const OverWriteFiles       = True
  const HKEY_CURRENT_USER    = &H80000001
  const HKEY_LOCAL_MACHINE   = &H80000002
  Const HKEY_USERS           = &H80000003 

' ------------------------------------------------------------------------------------------------
' Create the log location (if not exists)
' Open the logfile.  
' ------------------------------------------------------------------------------------------------

  CreateFolderStructure(strLogLocation)
  OpenLogFile()
  WriteToLog "- ACTION: script started."
  WriteToLog " "

' ------------------------------------------------------------------------------------------------
' Detect the current OS.  
' ------------------------------------------------------------------------------------------------

  Set colItems = objWMIService.ExecQuery("Select Caption,BuildNumber from Win32_OperatingSystem")
  For Each objItem in colItems
      strOS            = objItem.Caption
      valOSBuildNumber = objItem.BuildNumber
  next

' ------------------------------------------------------------------------------------------------
' Find the currently logged on userid.
' And translate it to the users SID.
' ------------------------------------------------------------------------------------------------

  Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process where Name = 'explorer.exe'")
  For Each objProcess in colProcess
      valReturnCode = objProcess.GetOwner(strCurrentUser)
      if valReturnCode <> 0 Then strCurrentUser = "Unable to find the username!"
  Next

  strCurrentUser = Ucase(strDNSDomain) & "\" & strCurrentUser

  strCurrentUserSID = GetSIDFromUser(strCurrentUser)

' ------------------------------------------------------------------------------------------------
' Detect the current processor architecture.  
' ------------------------------------------------------------------------------------------------

  if objFSO.FolderExists(objShell.ExpandEnvironmentStrings("%windir%") & "\SysWOW64\Config") then  
     strArchitecture = "x64"
       else
     strArchitecture = "x86"
  end if

' ------------------------------------------------------------------------------------------------
' Detect the current User Interface MUI.  
' ------------------------------------------------------------------------------------------------

  strDetectUserUILanguage = DetectUserUILanguage

  strInstallLanguage = DetectUserUILanguage


  WriteToLog("########## Details regarding operating system and logged on user     ##########")
  WriteToLog("Found Operating System:       " & strOS)
  WriteToLog("Found architecture:           " & strArchitecture)
  WriteToLog("Logged on userid:             " & strCurrentUser)
  WriteToLog("Logged on user SID:           " & strCurrentUserSID)
  WriteToLog("User MUI Language:            " & DetectUserUILanguage)
  WriteToLog("########## End details regarding operating system and logged on user ##########")
  WriteToLog(" ")

  WriteToLog("########## Mount all AppV 5.0 packages into the AppV Cache and       ##########")

' ------------------------------------------------------------------------------------------------
' Start the installation
' ------------------------------------------------------------------------------------------------ 

  EnablePackageScriptsViaRegistry

  strCommand = "powershell -ExecutionPolicy Bypass import-module 'C:\Program Files\Microsoft Application Virtualization\Client\AppvClient\AppvClient.psd1'; Mount-AppvClientPackage -Name * -verbose"
  WriteToLog("Running command: " & strCommand)
  valReturnCode = objShell.Run(strCommand,6,True)
  WriteToLog("Result: " & valReturnCode)

  WriteToLog("########## End Mounting all AppV 5.0 packages.                       ##########")
  WriteToLog(" ")
  CloseLogFile()
  wscript.quit 0


Sub OpenLogFile() 

' ------------------------------------------------------------------------------------------------
' Subroutine: OpenLogFile() 
' The name of the logfile is mentinoed in the variabele strOutputFile.
' ------------------------------------------------------------------------------------------------

  If objLogFileFSO.FileExists(strOutputFile) Then
     Set objLogFile = objLogFileFSO.OpenTextFile(strOutputFile, ForWriting)
         Else
     Set objLogFile = objLogFileFSO.CreateTextFile(strOutputFile)
  End If

End Sub

Sub CloseLogFile()

' ------------------------------------------------------------------------------------------------
' Subroutine: CloseLogFile()
' Close the log file.
' ------------------------------------------------------------------------------------------------

  WriteToLog "- ACTION: script ended."
  objLogFile.Close
  Set objLogfileFSO = Nothing

End Sub

Function WriteToLog(sLogMessage)

' ------------------------------------------------------------------------------------------------
' Function: WriteToLog(sLogMessage)
' Writes an entry 'sLogMessage' in the logfile.
' ------------------------------------------------------------------------------------------------

  if instr(sLogMessage, "- ACTION: ") = 0 then sLogMessage = "          " & sLogMessage
  objLogFile.WriteLine("Time: " & now & "  " & sLogMessage)

End Function

Function GetSIDFromUser(UserName)

' ------------------------------------------------------------------------------------------------
' Function: GetSIDFromUser(UserName)
' Gets the SID from the give username.
' We use the registry to avoid an empty result if run under the SYSTEM account.
' ------------------------------------------------------------------------------------------------

  Dim DomainName, Result, WMIUser

  If InStr(UserName, "\") > 0 Then
    DomainName = Mid(UserName, 1, InStr(UserName, "\") - 1)
    UserName = Mid(UserName, InStr(UserName, "\") + 1)
  Else
    DomainName = CreateObject("WScript.Network").UserDomain
  End If

  On Error Resume Next
  Set WMIUser = GetObject("winmgmts:{impersonationlevel=impersonate}!" _
    & "/root/cimv2:Win32_UserAccount.Domain='" & DomainName & "'" _
    & ",Name='" & UserName & "'")
  If Err.Number = 0 Then
    Result = WMIUser.SID
  Else
    Result = ""
  End If
  On Error GoTo 0

  GetSIDFromUser = Result

End Function

Sub CreateFolderStructure(strFolderNameToBeCreated)

' ------------------------------------------------------------------------------------------------
' Subroutine: CreateFolderStructure(strFolderNameToBeCreated)
' Creates the map as mentioned in strFolderNameToBeCreated.
' ------------------------------------------------------------------------------------------------

  Dim arrFoldersTMP : arrFoldersTMP = split (strFolderNameToBeCreated,"\")
  Dim strFolder  : strFolder  = ""
  Dim objFolderTMP
 
  For Each objFolderTMP in arrFoldersTMP
      strFolder = strFolder & objFolderTMP
      If NOT objFSO.FolderExists(strFolder) Then
             objFSO.CreateFolder(strFolder)
      end If
      strFolder = strFolder & "\"
  Next
 
End Sub

Function DetectUserUILanguage

' ------------------------------------------------------------------------------------------------
' Function: DetectUserUILanguage
' Detects the User Interface Language 
' Modified by Willem-Jan Vroom d.d 12 Feb 14: also detects Czech, Finnish, Hungarian, Polish,
' Russian, Slovak, Slovian and Turkish languages on Windows 7.
' ------------------------------------------------------------------------------------------------
  
  if instr(lcase(strOS),"windows xp") > 0 then
     strKeyPath               = strCurrentUserSID & "\Control Panel\Desktop"
     strValueName             = "MultiUILanguageId"
     strLanguage              = "eng"
     objReg.GetStringvalue HKEY_USERS,strKeyPath,strValueName,strValue
     if NOT IsEmpty(strValue) Then
        strValue = UCase(right(strValue,4))
        if strValue = "040C" then strLanguage = "fra"
        if strValue = "0410" then strLanguage = "ita"
        if strValue = "0407" then strLanguage = "ger"
        if strValue = "040A" then strLanguage = "spa"
        if strValue = "0C0A" then strLanguage = "spa"
     end if
      else
     strKeyPath               = strCurrentUserSID & "\Control Panel\Desktop"
     strValueName             = "PreferredUILanguages"
     strLanguage              = "eng"
     objReg.GetMultiStringValue HKEY_USERS,strKeyPath,strValueName,arrValues
     if not IsNull(arrValues) Then
        strValue=arrValues(0)
        if NOT IsEmpty(strValue) then
           strValue = lcase(left(strValue,2))
           if strValue = "fr" then strLanguage = "fra"
           if strValue = "it" then strLanguage = "ita"
           if strValue = "de" then strLanguage = "ger"
           if strValue = "es" then strLanguage = "spa"
           if strValue = "cs" then strLanguage = "cze"
           if strValue = "fi" then strLanguage = "fin"
           if strValue = "hu" then strLanguage = "hun"
           if strValue = "pl" then strLanguage = "pol"
           if strValue = "ru" then strLanguage = "rus"
           if strValue = "sk" then strLanguage = "sky"
           if strValue = "sl" then strLanguage = "slv"
           if strValue = "sv" then strLanguage = "swe"
           if strValue = "tr" then strLanguage = "tur"
        end if
     end if
     if strLanguage = "eng" then
        strKeyPath               = ".DEFAULT\Control Panel\Desktop\MuiCached"
        strValueName             = "MachinePreferredUILanguages"
        objReg.GetMultiStringValue HKEY_USERS,strKeyPath,strValueName,arrValues
        if not IsNull(arrValues) Then
           strValue=arrValues(0)
           if NOT IsEmpty(strValue) then
              strValue = lcase(left(strValue,2))
              if strValue = "fr" then strLanguage = "fra"
              if strValue = "it" then strLanguage = "ita"
              if strValue = "de" then strLanguage = "ger"
              if strValue = "es" then strLanguage = "spa"
              if strValue = "cs" then strLanguage = "cze"
              if strValue = "fi" then strLanguage = "fin"
              if strValue = "hu" then strLanguage = "hun"
              if strValue = "pl" then strLanguage = "pol"
              if strValue = "ru" then strLanguage = "rus"
              if strValue = "sk" then strLanguage = "sky"
              if strValue = "sl" then strLanguage = "slv"
              if strValue = "sv" then strLanguage = "swe"
              if strValue = "tr" then strLanguage = "tur"
           end if
        end if
     end if     
  end if

  DetectUserUILanguage = strLanguage
End Function

Function fnKillProcess(strProcessName)

' ------------------------------------------------------------------------------------------------
' Function: fnKillProcess(strProcessName)
' Terminates the given processname.
' ------------------------------------------------------------------------------------------------

  Set colProcess = fn_objWMIService.ExecQuery ("Select * From Win32_Process")
  For Each objProcess In colProcess
    If Instr(LCase(objProcess.Name),LCase(strProcessName)) > 0 Then
       objShell.Run "TASKKILL /F /T /IM " & objProcess.Name, 0, False
       objProcess.Terminate()
       WriteToLog("Terminating application: " & objProcess.Name)
    End If
  Next

End Function

Function fnCheckIsMSIInstalled(strMSIName)

  ' -----------------------------------------------------------------------------------------------------------------
  ' Function: fnCheckIsMSIInstalled(strMSIName)
  ' Checks if the MSI given in strMSIName has been installed.
  ' strMSIName = "\\server\share\path\to\install.msi"
  ' This script is based on the example in 
  ' http://www.pcreview.co.uk/forums/getting-property-info-via-vbscript-text-file-t1544137.html
  ' -----------------------------------------------------------------------------------------------------------------

  Dim fn_objFSO    : Set fn_objFSO = CreateObject("Scripting.FileSystemObject")
  Dim fn_objWI     : Set fn_objWI  = CreateObject("WindowsInstaller.Installer")
 
  Dim fn_objReg    : Set fn_objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
  Dim strKeyPath   : strKeyPath    = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
  Dim strValueName : strValueName  = "InstallDate"

  ' -----------------------------------------------------------------------------------------------------------------
  ' check if the file exists and that the given file is a MSI
  ' -----------------------------------------------------------------------------------------------------------------

  If NOT fn_objFSO.FileExists(strMSIName) then
     ' --------------------------------------------------------------------------------------------------------------
     ' Leave the function and clean up the mess.
     ' --------------------------------------------------------------------------------------------------------------
     fnCheckIsMSIInstalled = False
     Set fn_objFSO     = Nothing
     Set fn_objWI      = Nothing
     Set fn_objReg     = Nothing
     Exit Function
  End If
  If lcase(right(strMSIName,3)) <> "msi" then
     ' --------------------------------------------------------------------------------------------------------------
     ' Leave the function and clean up the mess.
     ' --------------------------------------------------------------------------------------------------------------
     fnCheckIsMSIInstalled = False
     Set fn_objFSO     = Nothing
     Set fn_objWI      = Nothing
     Set fn_objReg     = Nothing
     Exit Function
  End If

  ' -----------------------------------------------------------------------------------------------------------------
  ' Find the property 'ProductCode' in the given MSI
  ' -----------------------------------------------------------------------------------------------------------------

  Dim fn_objDB     : Set fn_objDB   = fn_objWI.OpenDatabase(strMSIName,2)
  Dim fn_objView   : Set fn_objView = fn_objDB.OpenView("Select `Value` From Property WHERE `Property` ='ProductCode'")
  fn_objView.Execute
  Dim fn_objRec    : Set fn_objRec  = fn_objView.Fetch
  If fn_objRec is Nothing Then
     ' --------------------------------------------------------------------------------------------------------------
     ' Leave the function and clean up the mess.
     ' --------------------------------------------------------------------------------------------------------------
     fnCheckIsMSIInstalled = False
     Set fn_objFSO     = Nothing
     Set fn_objWI      = Nothing
     Set fn_objReg     = Nothing
     Set fn_objDB      = Nothing
     Set fn_objView    = Nothing
     Set fn_objRec     = Nothing
     Exit Function
  End If
  Dim strProductCode : strProductCode = fn_objRec.StringData(1)

  ' -----------------------------------------------------------------------------------------------------------------
  ' Check that the key InstallDate in the hive HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{ProductCode} 
  ' exists
  ' -----------------------------------------------------------------------------------------------------------------

  strKeyPath = strKeyPath & strProductCode
  fn_objReg.GetStringValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue
  if IsNull(strValue) then 
     fnCheckIsMSIInstalled = False
       else
     fnCheckIsMSIInstalled = True
  end if
  
  Set fn_objFSO     = Nothing
  Set fn_objWI      = Nothing
  Set fn_objReg     = Nothing
  Set fn_objDB      = Nothing
  Set fn_objView    = Nothing
  Set fn_objRec     = Nothing
  
End Function

Function KeyExists(Key, KeyPath)

' ------------------------------------------------------------------------------------------------
' Function: KeyExists(Key, KeyPath)
' Checks to see if the 'Key' in 'KeyPath' exists.
' ------------------------------------------------------------------------------------------------

  Dim arrSubKeys
  Dim i
  Dim regReadResult : regReadResult = objReg.EnumKey(HKEY_LOCAL_MACHINE, KeyPath, arrSubKeys)
  
  	
  KeyExists = False
    
  If regReadResult = 0 And IsArray(arrSubKeys) Then
     For i = 0 To UBound(arrSubKeys)
         If InStr(arrSubKeys(i), Key) <> 0 Then
       	    KeyExists = True
    	    i = UBound(arrSubKeys)
	 End If
     Next	
  End If
    
End Function

Function ValueExists(Key, KeyPath)

' ------------------------------------------------------------------------------------------------
' Function: ValueExists(Key, KeyPath)
' Checks to see if the value 'Key' in 'KeyPath' exists.
' ------------------------------------------------------------------------------------------------

  Dim arrSubValues
  Dim i
  Dim regReadResult : regReadResult = objReg.EnumValues(HKEY_LOCAL_MACHINE, KeyPath, arrSubValues)

  	
  ValueExists = False
    
  If regReadResult = 0 And IsArray(arrSubValues) Then
     For i = 0 To UBound(arrSubValues)
         If InStr(arrSubValues(i), Key) <> 0 Then
    	    ValueExists = True
    	    i = UBound(arrSubValues)
         End If
     Next	
  End If

End Function


Sub EnablePackageScriptsViaRegistry

' ------------------------------------------------------------------------------------------------
' Set 'EnablePackageScripts' via the registry. 
' ------------------------------------------------------------------------------------------------

  WriteToLog("########## Modify 'EnablePackageScripts' via the registry.            ##########")

  strKeyPath   = "SOFTWARE\Microsoft\AppV\Client\Scripting"
  strValueName = "EnablePackageScripts"
  objReg.SetDWordValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,1
  wscript.sleep 1000
  objReg.GetDWordValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,dwValue
  WriteToLog("The key HKEY_LOCAL_MACHINE\" & strKeyPath)
  WriteToLog("   Value: " & strValueName & " has the value: " & dwValue)

  WriteToLog("########## End modifying 'EnablePackageScripts' via the registry.     ##########")

End Sub

After the




Load all AppV 4.6 packages into the AppV Cache

After the migration from SCCM 2007 to SCCM 2012 some users had an issue with accessing their AppV 4.6 packages. They received the following error:

Error message that AppV 46 cannot be found in ccmcache.

Error message that AppV 46 cannot be found in ccmcache.


What happens. During the client migration the ccmcache is cleared. So if an AppV 4.6 package has not been started before the migration, the content could not be found after starting the package after the migration.
That has been solved by creating a vbscript that is run before the SCCM 2007 client is migrated to SCCM 2012. In short: for each AppV 4.6 application the virtual package is loaded into the AppV Cache.

' ================================================================================================
' Load all the AppV 4.6 Packages again
' Created by Willem-Jan Vroom
' (c) Canon Europe B.V.
' Version history:
'
' v0.0.1:
'   Initial version
'
' v0.0.2:
'   Add functionality:
'     - Unlocks the AppV 4.6 package if it is in use
'     - For each entry a log file is created. 
' ================================================================================================

' ------------------------------------------------------------------------------------------------
' Declare the most variables. 
' ------------------------------------------------------------------------------------------------

  Option Explicit

  Dim objShell               : set objShell         = WScript.CreateObject("WScript.Shell")
  Dim objNetWork             : set objNetWork       = Wscript.CreateObject("WScript.Network")
  Dim objLogFileFSO          : Set objLogFileFSO    = CreateObject("Scripting.FileSystemObject")
  Dim objFSO                 : Set objFSO           = CreateObject("Scripting.FileSystemObject")
  Dim objProcessEnv          : Set objProcessEnv    = objShell.Environment("PROCESS")
  Dim objWMIService          : Set objWMIService    = GetObject("winmgmts:\\.\root\cimv2")
  Dim objReg                 : Set objReg           = GetObject("winmgmts:\\.\root\default:StdRegProv")
  Dim objLogFile
  Dim colListOfServices
  Dim objService                
  Dim strCurrentDir          : strCurrentDir        = Left(Wscript.ScriptFullname, InstrRev(Wscript.ScriptFullname, "\"))
                               strCurrentDir        = Left(strCurrentDir,len(strCurrentDir)-1)
  Dim strCommand             : strCommand           = ""
  Dim strLogLocation         : strLogLocation       = "C:\WINDOWS\system32\Logfiles"
  Dim strcomputerName        : strcomputerName      = objProcessEnv("COMPUTERNAME")
  Dim strOutputFile          : strOutputFile        = strLogLocation & "\" & strcomputerName & "_ReinstallAllAppV46Packages_" & Replace(FormatDateTime(Now(), 2),"/","-") & ".log"
  Dim strOS                  : strOS                = ""
  Dim strKeyPath             : strKeyPath           = ""
  Dim strArchitecture        : strArchitecture      = "x86"
  Dim strWindir              : strWinDir            = objShell.ExpandEnvironmentStrings("%WinDir%")

  Dim valOSBuildNumber, valResult
  Dim objItem, colItems
  Dim arrSubKeys, objSubKey
  Dim strProcess

  Const ForWriting           = 2
  Const ForReading           = 1
  Const ForAppending         = 8
  Const OverWriteFiles       = True
  const HKEY_CURRENT_USER    = &H80000001
  const HKEY_LOCAL_MACHINE   = &H80000002
  Const HKEY_USERS           = &H80000003 

' ------------------------------------------------------------------------------------------------
' Detect the current OS architecture.  
' ------------------------------------------------------------------------------------------------

  if objFSO.FolderExists(strWindir & "\syswow64") Then
     strArchitecture = "x64"
  end if

' ------------------------------------------------------------------------------------------------
' Create the log location (if not exists)
' Open the logfile.  
' ------------------------------------------------------------------------------------------------

  CreateFolderStructure(strLogLocation)
  OpenLogFile()
  WriteToLog "- ACTION: script started."

' ------------------------------------------------------------------------------------------------
' Detect the current OS.  
' ------------------------------------------------------------------------------------------------

  Set colItems = objWMIService.ExecQuery("Select Caption,BuildNumber from Win32_OperatingSystem")
  For Each objItem in colItems
      strOS            = objItem.Caption
      valOSBuildNumber = objItem.BuildNumber
  Next

  WriteToLog("########## Details regarding operating system and logged on user        ##########")
  WriteToLog("Found Operating System:    " & strOS)
  WriteToLog("Found OS architecture:     " & strArchitecture)
  WriteToLog("Wscript engine:            " & wscript.FullName)
  WriteToLog("########## End details regarding operating system and logged on user    ##########")
  WriteToLog(" ")

' ------------------------------------------------------------------------------------------------
' Kill all the processes that belongs to the virtual applications
' The location of the packages are:
' on x86:
'  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SoftGrid\4.5\Client\Applications
' 
' on x64:
'  HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\SoftGrid\4.5\Client\Applications
' ------------------------------------------------------------------------------------------------

  if strArchitecture = "x64" then
     strKeyPath = "SOFTWARE\Wow6432Node\Microsoft\SoftGrid\4.5\Client\Applications"
      else
     strKeyPath = "SOFTWARE\Microsoft\SoftGrid\4.5\Client\Applications"
  end if

  WriteToLog ("---------------------------------------------------------------------------------")
  WriteToLog ("Closing all applications that belongs to an AppV 4.6 package.")

  objReg.EnumKey HKEY_LOCAL_MACHINE, strKeyPath, arrSubKeys
  if IsArray(arrSubKeys) Then
     For Each objSubKey in arrSubKeys
         KillProcessFromAllvirtualPackages(strKeyPath & "\" & objSubKey)
     Next
     else 
    WriteToLog("No AppV 4.6 packages have been found.")
  end if
  WriteToLog ("---------------------------------------------------------------------------------")
  WriteToLog (" ")


' ------------------------------------------------------------------------------------------------
' Load the App-V 4.6 packages. 
' The location of the packages are:
' on x86:
'  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SoftGrid\4.5\Client\Packages
' 
' on x64:
'  HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\SoftGrid\4.5\Client\Packages
' ------------------------------------------------------------------------------------------------

  if strArchitecture = "x64" then
     strKeyPath = "SOFTWARE\Wow6432Node\Microsoft\SoftGrid\4.5\Client\Packages"
      else
     strKeyPath = "SOFTWARE\Microsoft\SoftGrid\4.5\Client\Packages"
  end if

  objReg.EnumKey HKEY_LOCAL_MACHINE, strKeyPath, arrSubKeys
  if IsArray(arrSubKeys) Then
     For Each objSubKey in arrSubKeys
         LoadAppV46Package(strKeyPath & "\" & objSubKey)
     Next
     else 
    WriteToLog("No AppV 4.6 packages have been found. Thus quitting.")
  end if

  CloseLogFile()


Sub OpenLogFile() 

' ------------------------------------------------------------------------------------------------
' Subroutine: OpenLogFile() 
' The name of the logfile is mentinoed in the variabele strOutputFile.
' ------------------------------------------------------------------------------------------------

  If objLogFileFSO.FileExists(strOutputFile) Then
     Set objLogFile = objLogFileFSO.OpenTextFile(strOutputFile, ForWriting)
         Else
     Set objLogFile = objLogFileFSO.CreateTextFile(strOutputFile)
  End If

End Sub


Function WriteToLog(sLogMessage)

' ------------------------------------------------------------------------------------------------
' Function: WriteToLog(sLogMessage)
' Writes an entry 'sLogMessage' in the logfile.
' ------------------------------------------------------------------------------------------------

  if instr(sLogMessage, "- ACTION: ") = 0 then sLogMessage = "          " & sLogMessage
  objLogFile.WriteLine("Time: " & now & "  " & sLogMessage)

End Function

Sub CloseLogFile()

' ------------------------------------------------------------------------------------------------
' Subroutine: CloseLogFile()
' Close the log file.
' ------------------------------------------------------------------------------------------------
  WriteToLog("########## End repairing all the AppV Packages.                         ##########")
  WriteToLog("- ACTION: script ended.")           
  objLogFile.Close
  Set objLogfileFSO = Nothing

End Sub


Sub CreateFolderStructure(strFolderNameToBeCreated)

' ------------------------------------------------------------------------------------------------
' Subroutine: CreateFolderStructure(strFolderNameToBeCreated)
' Creates the map as mentioned in strFolderNameToBeCreated.
' ------------------------------------------------------------------------------------------------

  Dim arrFoldersTMP : arrFoldersTMP = split (strFolderNameToBeCreated,"\")
  Dim strFolder     : strFolder     = ""
  Dim objFolderTMP

  For Each objFolderTMP in arrFoldersTMP
      strFolder = strFolder & objFolderTMP
      If NOT objFSO.FolderExists(strFolder) Then
             objFSO.CreateFolder(strFolder)
      end If
      strFolder = strFolder & "\"
  Next

End Sub

Sub KillProcessFromAllvirtualPackages(key)

' ------------------------------------------------------------------------------------------------
' Subroutine: LoadAppV46Package(key)
' ------------------------------------------------------------------------------------------------

  Dim strProcessNameToKill : objReg.GetStringValue HKEY_LOCAL_MACHINE, key, "AppPath", strProcessNameToKill

  if not IsNull(strProcessNameToKill) Then

     Dim numFirstChar : numFirstChar = len(left(strProcessNameToKill, InstrRev(strProcessNameToKill, "\")))
     Dim numLastChar  : numLastChar  = len(strProcessNameToKill)

     call fnKillProcess(mid(strProcessNameToKill,numFirstChar+1,numLastChar))
  end if

End Sub



Sub LoadAppV46Package(key)

' ------------------------------------------------------------------------------------------------
' Subroutine: LoadAppV46Package(key)
' ------------------------------------------------------------------------------------------------

  Dim strSoftGridPackageName  : objReg.GetStringValue HKEY_LOCAL_MACHINE, key, "Name",        strSoftGridPackageName
  Dim strSoftGridSFTName      : objReg.GetStringValue HKEY_LOCAL_MACHINE, key, "OverrideURL", strSoftGridSFTName
  Dim strCommand


  WriteToLog ("---------------------------------------------------------------------------------")
  WriteToLog ("Processing SCCM AppV Package: " & strSoftGridPackageName & ".")

  

  strCommand = "sftmime unlock package:" & strSoftGridPackageName & " /LOG c:\windows\system32\logfiles\Unlock_AppV46Package_" & strSoftGridPackageName & ".log"
  WriteToLog ("Running command: " & ucase(strCommand))

  On Error Resume Next
  objShell.Run strCommand,6,True
  If Err.Number <> 0 Then
     WriteToLog("The following error occurred: " & Err.Description)
  end if
  On Error Goto 0
  
  On Error Resume Next
  strCommand = "sftmime unload package:" & strSoftGridPackageName & " /LOG c:\windows\system32\logfiles\Unload_AppV46Package_" & strSoftGridPackageName & ".log"
  WriteToLog ("Running command: " & ucase(strCommand))
  objShell.Run strCommand,6,True
  If Err.Number <> 0 Then
     WriteToLog("The following error occurred: " & Err.Description)
  end if
  On Error Goto 0

  On Error Resume Next
  strCommand = "sftmime load package:" & strSoftGridPackageName & " /sftpath "
  strCommand = strCommand & chr(34) & strSoftGridSFTName & chr(34) & " /LOG c:\windows\system32\logfiles\Load_AppV46Package_" & strSoftGridPackageName & ".log"
  WriteToLog ("Running command: " & ucase(strCommand))
  objShell.Run strCommand,6,True
  If Err.Number <> 0 Then
     WriteToLog("The following error occurred: " & Err.Description)
  end if
  On Error Goto 0


  On Error Goto 0

  WriteToLog ("End processing SCCM AppV Package: " & strSoftGridPackageName & ".")
  WriteToLog ("---------------------------------------------------------------------------------")
  WriteToLog (" ")
  

End Sub

Function fnKillProcess(strProcessName)

' ------------------------------------------------------------------------------------------------
' Function: fnKillProcess(strProcessName)
' Terminates the given processname.
' ------------------------------------------------------------------------------------------------

  Dim colProcess
  Dim objProcess

  Set colProcess = objWMIService.ExecQuery ("Select * From Win32_Process")
  For Each objProcess In colProcess
    If Instr(LCase(objProcess.Name),LCase(strProcessName)) > 0 Then
       objShell.Run "TASKKILL /F /T /IM " & objProcess.Name, 0, False
       objProcess.Terminate()
       WriteToLog("Terminating application: " & objProcess.Name)
    End If
  Next

End Function

Sub CheckProcess(strProcess)
' ------------------------------------------------------------------------------------------------
' Subroutine: CheckProcess(strProces)
' checks if a process is running. The subroutine is left when the process is not running
' anymore.
' ------------------------------------------------------------------------------------------------

  Dim blnProcessFound    : blnProcessFound = True
  Dim intLoopNotFound    : intLoopNotFound = 0
  Dim MINLoopCount       : MINLoopCount    = 3
  Dim objSWbemServices
  Dim colSWbemObjectSet
  Dim objSWbemObject
  Dim strCmdLine
  
  strProcess = ucase(strProcess)
  
  Do While (blnProcessFound And intLoopNotFound < 3)
    blnProcessFound = False
    WScript.Sleep 3000
    Set objSWbemServices = GetObject("winmgmts:\\.\root\cimv2")
    Set colSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Process")
    For Each objSWbemObject In colSWbemObjectSet
        If (ucase(objSWbemObject.Name) = strProcess) And  (strCmdLine = "" Or strCmdLine = objSWbemObject.CommandLine) then
            blnProcessFound = true
                    intLoopNotFound = 0
        End If
    Next
    If Not blnProcessFound Then intLoopNotFound = intLoopNotFound + 1
    Set colSWbemObjectSet = Nothing
    Set objSWbemServices  = Nothing
  Loop

End Sub

Now the user can start the application without issues:

 Successfull start.

Successfull start.




Uninstall AppV 5.0 package automatically if a computer is removed from a device based collection in SCCM 2012

In my article Uninstall AppV 5.0 package automatically if an user is removed from a user based collection in SCCM 2012 I described how an application is removed if an user is removed from an user based collection.
In this article I will describe how an application is uninstalled if a computer is removed from a device based collection.

Create the computer based uninstall query

On page 30 in the document Managing AppV5 with Configuration Manager 2012SP1 it is described how a computer based uninstall collection is made.

The uninstall query.

The uninstall query.

The query is:

select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client
from
SMS_R_System inner join SMS_G_System_AppClientState on SMS_G_System_AppClientState.MachineName = SMS_R_System.Name
where 
SMS_G_System_AppClientState.ComplianceState = "1"
and 
SMS_G_System_AppClientState.AppName ="Mozilla_Firefox_24_DUT_WIN7"

Create the uninstall deployment

The application ‘Mozilla_Firefox_24_DUT_WIN7’ has both an install and uninstall deployment.

The application has both an install and uninstall deployment.

The application has both an install and uninstall deployment.




Uninstall AppV 5.0 package automatically if an user is removed from a user based collection in SCCM 2012

With SCCM 2007 you had the option to automatic uninstall virtual applications. In the package you selected ‘Remove this package from clients when it is no longer advertised’. If the user was no longer member of the collection the virtual application was removed.

SCCM 2007 Remove this package from clients when it is no longer advertised.

SCCM 2007 Remove this package from clients when it is no longer advertised.

With SCCM 2012 this feature has been discontinued.

In SCCM 2012 the trick is to create a collection that contains all the users that are no longer member of the group that belongs to the application.

In this example the application ‘Demo_MoreShortcuts_1.0_ENG – TEST’ will be removed by the users who are no longer member of the Active Directory group ‘APPL – Demo_TST’

The start position

The shortcut is visible for the user:

Application is visible in Start Menu.

Application is visible in Start Menu.

Create a query for the user based collection

Query to find the users who are no longer entitled to use this application.

Query to find the users who are no longer entitled to use this application.

The query:

select SMS_R_USER.ResourceID,SMS_R_USER.ResourceType,SMS_R_USER.Name,SMS_R_USER.UniqueUserName,SMS_R_USER.WindowsNTDomain
from 
SMS_R_User inner join SMS_G_System_AppClientState on SMS_R_USER.UniqueUserName = SMS_G_System_AppClientState.UserName
where
SMS_G_System_AppClientState.AppName = "Demo_MoreShortcuts_1.0_ENG - TEST" 
and
G_System_AppClientState.ComplianceState = 1 and SMS_R_USER.UniqueUserName not in 
(select distinct SMS_R_USER.UniqueUserName 
from 
SMS_R_User 
where
UserGroupName = "DEMOFOREST\\APPL - Demo_TST")

The most important parts are:

  1. SMS_G_System_AppClientState.AppName = “Demo_MoreShortcuts_1.0_ENG – TEST” and G_System_AppClientState.ComplianceState = 1
    Query is based on the Application Name in CM12.

    Query is based on the Application Name in CM12.


    More information can be found at SMS_G_System_AppClientState Server WMI Class
  2. SMS_R_USER.UniqueUserName not in (select distinct SMS_R_USER.UniqueUserName from SMS_R_User where UserGroupName = “DEMOFOREST\\APPL – Demo_TST”)

The query does not return any users:

The query does not give any results.

The query does not give any results.

The application has both a install and uninstall deployment attached to it.

The application has both an install and uninstall deployment.

The application has both an install and uninstall deployment.

User management

Remove the user from the group and perform all client actions on the client.

Remove the user from the group.

Remove the user from the group.

After a while the user who was member of the group is now visible in the collection:

After a while the removed user is visible in the collection.

After a while the removed user is visible in the collection.

If you install Configuration Manager Support Center on a server, you can open both the AppIntentEval.log and the AppEnforce.log. It shows as a ProhibitedApplication in the AppIntentEval.log.

The AppIntentEval log and AppEnforce log (1 of 2).

The AppIntentEval log and AppEnforce log (1 of 2).

The AppIntentEval log and AppEnforce log (2 of 2).

The AppIntentEval log and AppEnforce log (2 of 2).

The virtual application has been uninstalled.




Multiple shortcuts with different access permissions in SCCM 2012

In my article ‘Multiple shortcuts with different access permissions‘ I explained how it is possible to manage an application with different shortcuts with different access permissions with the Application Virtualization web interface. Now I will explain how this is done with SCCM 2012 R2 with CU2.

The basic information can be found in the document ‘Managing AppV5 with Configuration Manager 2012SP1‘. In short: for each application you will have one appv file, one deployment config xml file and one user deployment config xml file.

For example: for the TEST environment you will have the following files on your SCCM Packages share:

  • Demo_MoreShortcuts_1.0_ENG.appv
  • Demo_MoreShortcuts_1.0_ENG_DeploymentConfig.xml
  • Demo_MoreShortcuts_1.0_ENG_UserConfig – TEST.xml

For example: for the PROD environment you will have the following files on your SCCM Packages share:

  • Demo_MoreShortcuts_1.0_ENG.appv
  • Demo_MoreShortcuts_1.0_ENG_DeploymentConfig.xml
  • Demo_MoreShortcuts_1.0_ENG_UserConfig – PROD.xml

In this article I will describe the steps for the PROD environment.

Create the collection

Create user collection

Create user collection

Enter collection name

Enter collection name

Direct rule

Direct rule

Click Next to continue.

Click Next to continue.

Fill in the AD group name. You can use willcards, like %.

Fill in the AD group name. You can use willcards, like %.

Choose the correct group name

Choose the correct group name

Click Next to continue.

Click Next to continue.

Close

Close

The AD group name has been filled in.  Click Next to continue.

The AD group name has been filled in.
Click Next to continue.

Click Next to continue.

Click Next to continue.

Click Close to finish User Collection Wizard

Click Close to finish User Collection Wizard

Update collection membership

Update collection membership.

Update collection membership.

Click Yes.

Click Yes.

The collection has been updated successfully. The AD group is visible now.

The collection has been updated successfully. The AD group is visible now.

Create the application

Create application.

Create application.

 

Select 'Microsoft Application Virtualization 5' as type.

Select ‘Microsoft Application Virtualization 5’ as type.

Browse to the appv file that you want to use.

Browse to the appv file that you want to use.

And click next.

And click next.

You see all the files that are used. Please mention the name of the user config xml file. That will be used later on.

You see all the files that are used. Please mention the name of the user config xml file. That will be used later on.

Modify software name - by adding the environment to the filename - and version.

Modify software name – by adding the environment to the filename – and version.

Click Next.

Click Next.

And click close when the operation has been completed.

And click close when the operation has been completed.

Distribute the content

Distribute content

Distribute content

Click next.

Click next.

Click Next.

Click Next.

Select distribution point.

Select distribution point.

Choose the distribution points you want to use.

Choose the distribution points you want to use.

Click next.

Click next.

Click Next.

Click Next.

Click close when the content has been distributed.

Click close when the content has been distributed.

Create the deployment

 

Deploy the content to the collection.

Deploy the content to the collection.

Click browse to select the collection.

Click browse to select the collection.

Select the collection.

Select the collection.

Click next.

Click next.

 

You see the associated distribution points.  Click next.

You see the associated distribution points.
Click next.

Purpose is required. It will be a mandatory deployment.

Purpose is required. It will be a mandatory deployment.

Click Next to continue.

Click Next to continue.

If needed modify the settings. Click Next.

If needed modify the settings.
Click Next.

If needed modify the settings. Click Next.

If needed modify the settings.
Click Next.

Click Next.

Click Next.

A summary is displayed. Click Next.

A summary is displayed.
Click Next.

 

Click Close.

Click Close.

Test on the client

Before performing these steps log in with a userid that is member of the group ‘APPL – Demo_PRD’.

After a policy refresh the application is visible in the 'Software Center' and will be installed..

After a policy refresh the application is visible in the ‘Software Center’ and will be installed.

After installing the application is visible in the Start Menu.

After installing the application is visible in the Start Menu.

And the correct environment is show

And the correct environment is show.

In the AppEnforce.log you see what commands are executed to install the application.  As the collection is user based, the application is deployed on a per user basis. And thus the user config xml is used.

In the AppEnforce.log you see what commands are executed to install the application.
As the collection is user based, the application is deployed on a per user basis. And thus the user config xml is used.

If you enter the powershell command 'Get-AppVClientApplication -Name *environment*' you see the settings as mentioned in the appv file.

If you enter the powershell command ‘Get-AppVClientApplication -Name *environment*’ you see the settings as mentioned in the appv file.

Retrieve the properties for the application 'Demo_MoreShortcuts_1.0_ENG - PROD'.

Retrieve the properties for the application ‘Demo_MoreShortcuts_1.0_ENG – PROD’.

End you will see the same name.

End you will see the same name.




Testing deployments for SCCM 2007 on x64 based computers

As a packager you always want to know if the application works as described in a intake document. Also you want to eliminate deployment issues.
I run into issues that the application installed perfectly – even under the system account – but with SCCM 2007 the deployment failed.

Lets take the following scenario:

  • Deployment:
    SCCM 2007
  • Clients:
    Windows 8 x64

The question is: why did the installation fail?

As an example I made a SCCM program that only starts cmd.exe. If the advertisement is run, the command ‘whoami’ and ‘set PROCESSOR_ARCHITECTURE’ are entered, this is shown on the screen:
SCCM2007_cmd
As you can see, the PROCESSOR_ARCHITECTURE is x86, even if the x64 cmd.exe is used.

  • Question:
    Why is that?
  • Answer:
    SCCM 2007 is only 32 bits. So that means if the 32 bits ccmexec.exe starts a process, all the subprocesses are also 32 bits! See the numbers ‘1’ and ‘2’ below:
    processes
  • Question:
    Now we know how SCCM is dealing with the processor architecture. But how can we simulate this?
  • Answer:
    Use psexec from a remote system and start cmd.exe with the command: psexec \\remotecomputer -i -s cmd.exe
    psexec_cmd_x64
    Now c:\windows\sytem32\cmd.exe is started… but the PROCESSOR_ARCHITECTURE is still x64. Not the correct behavior, as we would like to have the 32 bits cmd.exe.
  • Question:
    How can we simulate the SCCM behavior so that the 32 bits cmd.exe is used?
  • Answer:
    Use psexec from a remote sytem and start c:\windows\syswow64\cmd.exe with the command: psexec \\remotecomputer -i -s c:\windows\syswow64\cmd.exe
    psexec_cmd_x86

Conclusion: use c:\windows\syswow64\cmd.exe together with psexec to test the installation of applications.
With SCCM 2012 that is no issue anymore as it has both 32 and 64 bits support.

Update: 16 July 2013

On How to workaround 64-bit redirection in SCCM 2007 (Microsoft TechNet Forum) I found an alternative. Use the following command line:

%windir%\sysnative\cmd.exe

Tested on Windows 7 x64:
sccm_2007_sysnative
(With a special thanks to Peter Fonk for pointing me to that Technet article.)