This article provides a PowerShell scripting template for SCCM and MDT packages. This template can also be used for stand-alone installations (without using SCCM or MDT)!
An SCCM package is basically a container with source files. I recommend using a wrapper (a script) to execute the installation files and for any configuration you may need. And that is exactly what the PowerShell template below offers!
Even better, the template in this article uses my PowerShell Function Library, which effectively turns it into a complete deployment framework.
Change Log 23.05.2017: updated and renamed functions. 05.06.2018: major overhaul of this article and single functions have been replaced by the Dennis Span PowerShell Function Library. 29.12.2019: solved an issue whereby the scripting template in an MDT task sequence does not work properly. I had to change the scope of the variables LogDir and LogFile in the scripting template. The scope for both of these variables is now set to global. If you are not familiar with the concept of scopes please see the Microsoft article About Scopes. |
Scripting Template
The scripting template below is an example that you can use for your installations and configurations. The functions in the template are imported from a PowerShell module for the Dennis Span PowerShell Function Library. The library offers more functions than included in the template (the template is only an example).
Please see the article PowerShell Function Library for detailed information how to install and use the library.
For a complete overview of all PowerShell functions included in the library, see the following articles on this website:
- Windows functions: PowerShell functions for Windows
- Citrix functions: PowerShell functions for Citrix
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
#========================================================================== # # <APPLICATION NAME> # # AUTHOR: <AUTHOR> # DATE : <DATE> # # COMMENT: <COMMENT> # # Note: see the article 'https://dennisspan.com/powershell-scripting-template-for-sccm-packages/' for a detailed description how to use this template # # Note: for an overview of all functions in the PowerShell function library 'DS_PowerShell_Function_Library.psm1' see: # -Windows functions: https://dennisspan.com/powershell-function-library/powershell-functions-for-windows/ # -Citrix functions: https://dennisspan.com/powershell-function-library/powershell-functions-for-citrix/ # # Change log: # ----------- # 29.12.2019 Dennis Span: the scope of the variables LogDir and LogFile is now set to "global". # This solves an issue when using the template in an MDT task sequence. #========================================================================== # Get the script parameters if there are any param ( # The only parameter which is really required is 'Uninstall' # If no parameters are present or if the parameter is not # 'uninstall', an installation process is triggered [string]$Installationtype ) # define Error handling # note: do not change these values $global:ErrorActionPreference = "Stop" if($verbose){ $global:VerbosePreference = "Continue" } ############################ # Preparation # ############################ # Disable File Security $env:SEE_MASK_NOZONECHECKS = 1 # Custom variables [edit] $BaseLogDir = "C:\Logs" # [edit] add the location of your log directory here $PackageName = "MyApp" # [edit] enter the display name of the software (e.g. 'Acrobat Reader' or 'Microsoft Office') # Global variables $StartDir = $PSScriptRoot # the directory path of the script currently being executed if (!($Installationtype -eq "Uninstall")) { $Installationtype = "Install" } $global:LogDir = (Join-Path $BaseLogDir $PackageName).Replace(" ","_") $LogFileName = "$($Installationtype)_$($PackageName).log" $global:LogFile = Join-path $LogDir $LogFileName # Create the log directory if it does not exist if (!(Test-Path $LogDir)) { New-Item -Path $LogDir -ItemType directory | Out-Null } # Create new log file (overwrite existing one) New-Item $LogFile -ItemType "file" -force | Out-Null # Import the Dennis Span PowerShell Function Library Import-Module "C:\Scripts\DS_PowerShell_Function_Library.psm1" DS_WriteLog "I" "START SCRIPT - $Installationtype $PackageName" $LogFile DS_WriteLog "-" "" $LogFile ############################ # Pre-launch commands # ############################ # Delete a registry value DS_DeleteRegistryValue -RegKeyPath "hklm:\SOFTWARE\MyApp" -RegValueName "MyValue" # Create a directory DS_CreateDirectory -Directory "C:\Temp\MyNewFolder" # Stop a service (+ dependencies) DS_StopService -ServiceName "Spooler" ############################ # Installation # ############################ # Install or uninstall software $FileName = "MyApp.msi" # [edit] enter the name of the installation file (e.g. 'MyApp.msi' or 'setup.exe') if ( $Installationtype -eq "Uninstall" ) { $Arguments = "" # [edit] enter arguments (for MSI file the following arguments are added by default: /i #File# /qn /norestart / l*v #LogFile#) } else { $Arguments = "Transforms=""MyApp.mst"" LANG_LIST=""en_US,de_DE""" # [edit] enter arguments (for MSI file the following arguments are added by default: /i #File# /qn /norestart / l*v #LogFile#) } $FileSubfolder = "Files" # [edit] enter the name of the subfolder which contains the installation file (e.g. 'Files' or 'MSI') $FileFullPath = Join-Path $StartDir $FileSubfolder # Concatenate the two directories $StartDit and $InstallFileFolder DS_InstallOrUninstallSoftware -File ( Join-Path $FileFullPath $FileName ) -InstallationType $Installationtype -Arguments $Arguments ############################ # Post-launch commands # ############################ # Delete a registry key DS_DeleteRegistryKey -RegKeyPath "hklm:\Software\MyApp" # Start a service (+ dependencies) DS_StartService -ServiceName "Spooler" ############################ # Finalize # ############################ # Enable File Security Remove-Item env:\SEE_MASK_NOZONECHECKS DS_WriteLog "-" "" $LogFile DS_WriteLog "I" "End of script" $LogFile |
How to use this template
First, copy the above code in your preferred editor (e.g. notepad) and save the file as a PowerShell script (*.PS1), for example MyAppInstaller.ps1. You can choose any file name you want of course.
You have to modify a couple of lines in the script to match your specific requirements. Enter your preferred log directory and package name in lines 44 and 45, for example:
- $BaseLogDir = “C:\Logs”
- $PackageName = “Adobe Acrobat Reader”
Or
- $BaseLogDir = “C:\Script\Logs”
- $PackageName = “Environment_Config”
1 2 3 |
# Custom variables [edit] $BaseLogDir = "C:\Logs" # [edit] add the location of your log directory here $PackageName = "Adobe Acrobat Reader" # [edit] enter the display name of the software (e.g. 'Acrobat Reader' or 'Microsoft Office') |
When the log directory is created, spaces are automatically replaced with an underscore (“_”), for example: C:\Logs\Adobe_Acrobat_Reader.
Tip: Changing the main log directory (e.g. C:\Logs) at a later time means that you have to change each script that contains the hard-coded path. There is another, more flexible way:
The PowerShell script now reads the log path from the environment variable GlobalLogPath. In case you ever want to change the log directory you simply change the path in the Group Policy Preference environment variable without having to change any of your scripts. |
The section How to use the library in a PowerShell script in the article PowerShell Function Library describes how to use the library in a PowerShell script. In line 61, make sure to enter the correct path to the library.
In the script sections “Pre-launch commands”, “Installation” and “Post-launch commands” make sure to enter the functions you require.
Optional: software (un)installation
In case you are installing or uninstalling software, add your source file(s) to a subfolder relative to the root folder (e.g. Files). The subfolder is the relative path to your package source directory. For example, if this is your package source folder:
\\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01
Than the subfolder for your installation source files is:
- \\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\Files
or
\\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\MSI
or - \\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\SETUP
In the PowerShell script, enter the name of your installation file, arguments and sub folder (again, relative to the root folder).
For example:
- Line 84: $FileName = “AcrobatReader.msi”
- Line 86/88: $Arguments =
- “Transforms=””AcrobatReader.mst”” LANG_LIST=””en_US,de_DE”””
or - “/silent /uninstall”
or - “/QN” (for setup.exe files only)
- “Transforms=””AcrobatReader.mst”” LANG_LIST=””en_US,de_DE”””
- Line 90: $FileSubFolder = “Files“
In the previous example, the folder Files is used for the variable $FileSubFolder, This is the default value, but you can use any folder name you want (e.g. “MSI”, or “SETUP”). Just make sure that the folder exists relative to the root of the path.
Please note that for MSI packages the following parameters are added by default:
- For uninstallations: “/x “”$File”” /qn /norestart /l*v “”$LogFileAPP”””
- For installations: “/i “”$File”” /qn /norestart /l*v “”$LogFileAPP”””
1 2 3 4 5 6 7 8 9 10 |
# Install or uninstall software $FileName = "MyApp.msi" # [edit] enter the name of the installation file (e.g. 'MyApp.msi' or 'setup.exe') if ( $Installationtype -eq "Uninstall" ) { $Arguments = "" # [edit] enter arguments (for MSI file the following arguments are added by default: /i #File# /qn /norestart / l*v #LogFile#) } else { $Arguments = "Transforms=""MyApp.mst"" LANG_LIST=""en_US,de_DE""" # [edit] enter arguments (for MSI file the following arguments are added by default: /i #File# /qn /norestart / l*v #LogFile#) } $FileSubfolder = "Files" # [edit] enter the name of the subfolder which contains the installation file (e.g. 'Files' or 'MSI') $FileFullPath = Join-Path $StartDir $FileSubfolder # Concatenate the two directories $StartDit and $InstallFileFolder DS_InstallOrUninstallSoftware -File ( Join-Path $FileFullPath $FileName ) -InstallationType $Installationtype -Arguments $Arguments |
When you have prepared your PowerShell file copy it in the root of your package source directory. For example:
\\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\AppInstaller.ps1
The PowerShell script is executed as follows:
- For installations:
powershell -file “\\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\AppInstaller.ps1″ Install - For uninstallations:
powershell -file “\\FileServer1\PackageSource\Adobe\Acrobat Reader\11.0.18_01\AppInstaller.ps1″ Uninstall
Happy scripting!
Dennis Span works as a Lead Account Technology Strategist at Cloud Software Group in Vienna, Austria. He holds multiple Citrix certifications (CCE-V). Dennis has been a Citrix Technology Advocate (CTA) since 2017 (+ one year as Citrix Technology Professional, CTP). Besides his interest in virtualization technologies and blogging, he loves spending time with his family as well as snowboarding, playing basketball and rowing. He is fluent in Dutch, English, German and Slovak and speaks some Spanish.
Great work, would this work for MDT as ell or it is just solely for SCCM?
Hi Phil, for sure it works with MDT as well. I do not add any SCCM specific functions in my PowerShell scripts. My scripts also work without SCCM or MDT. You can simply trigger them manually (e.g. powershell.exe -file %FileName%).
Pingback: Google Chrome on Citrix deep-dive - Dennis Span
Pingback: Scripting the complete list of Citrix components with PowerShell - Dennis Span
Pingback: Printer Drivers Installation and Troubleshooting Guide - Dennis Span
Pingback: Citrix Delivery Controller unattended installation with PowerShell and SCCM - Dennis Span
Pingback: Citrix License Server unattended installation with PowerShell and SCCM - Dennis Span
Pingback: Citrix Provisioning Server unattended installation - Dennis Span
Pingback: Citrix Director unattended installation with PowerShell - Dennis Span
Pingback: Citrix App Layering Agent unattended installation - Dennis Span
Pingback: How to configure and run BIS-F in an SCCM task sequence - Dennis Span
Pingback: Citrix StoreFront unattended installation with PowerShell - Dennis Span
Pingback: Citrix Receiver unattended installation with PowerShell - Dennis Span
Hello Dennis,
Thanks for your work!
I have a problem with SCCM, the package return an error 196608.
It works if i run manually the script.
Have you any idea for this error?
Hi Xavier, thanks!
I am not sure what that error means exactly, but I suggest to check the following:
-Test the installation as a system user (example: https://dennisspan.com/scripting-the-complete-list-of-citrix-components-with-powershell/#CFsDep2.sys).
-Also, the process ‘smsswd.exe’ that executes the package runs as a 32-bit process. Make sure that when testing manually you emulate this behavior. Open a 32-bit command window (C:\Windows\SysWOW64\cmd.exe) and run the installation from there.
In case you require more help, please send me an e-mail (dennis@dennisspan.com) with more details (e.g. exact command line, package settings, etc.). Perhaps you can even send me a copy of the script so I can test it myself.
Pingback: Encrypting passwords in a PowerShell script - Dennis Span
Pingback: Citrix Application Probe Agent unattended installation - Dennis Span
Hi,
in case you want to install a msi in the post (or pre) section. How do you implement that?
In my case the logfile is not fully written (broken actually), SCCM says Installation Failed and Post-msi is not installed.
THx for your reply
Hi Lieven, please send me the code snippet or the script to my e-mail (dennis@dennisspan.com). I can take a look at it if you want to.
Bye,
Dennis
Hi Dennis,
I found my mistake by running the .ps1 manually in powershell
PS: A nice feature would be a function that searches for a running process and kills it if specified. E.g. DS_SearchRunningProces -process iexplore.exe -Kill yes/no
Hi Lieven,
I am happy you found the mistake. And your idea for a new function to kill a running process is a good idea. I will implement it in the next release of my Function Library.
Thanks!
Bye,
Dennis
Thank you very much Dennis for sharing this knowledge. Please, i want to know. If i have to install multiple applications (ie acrobat, office, antivirus etc.), how do i go about it.
Thanks
Thanks! I am not sure I understand your question. What exactly would you like to know?
You would have make multiple copies of Dennis’ template and adapt each copy to the specific needs of the specific application installer.
Hi Dennis, great work, i highly appreciate it. One thing i would like to ask is: how do you feel about the powershell appdeploy toolkit? I feel this goes even a step further than you have done. Would it be a good suggestion if you would “hook on” to that one and adapt your function library to the psappdeploy toolkit? I do ask this because i feel your scripting template and the psappdeploy-toolkit serve the same purpose.
Hi Paul,
We used the AppDeploy Toolkit at my previous job. It is a very good PowerShell library, but I decided that I wanted something a bit less complicated, a bit “cleaner” if you will. I am talking about a simpler code structure of the PowerShell framework. For example, the AppDeploy Toolkit includes various features to support interactive installations (e.g. on end user’s devices). For a Citrix environment, this is not required. There is no interaction when updating the golden master image; everything is done in system context. I also wanted the log files to be more “readable”. In the end, there were various reasons for me to create my own library. But as said, the AppDeploy Toolkit is very good and in the end, every administrator should choose his or her tool of preference.