There may be a requirement to copy files to specific locations pre- or post-application deployment. An application may require customization through a pre-configured INI (configuration) file for example, or some supporting documentation must be copied to the user’s Documents folder, or perhaps additional corporate backgrounds must be deployed as part of a Microsoft Teams installation.
There can be many reasons why you may need to copy additional files along with the deployed application. In this chapter, you will learn where to place the files and how to reference them in the PowerShell deployment script depending on their location within the root folder structure.
Where Is This Script Running from Anyway?
If you always knew the file location your deployed script was in and the source files it referenced, it would be a fantastic thing indeed. If you knew the script was deployed to a client computer, executed from C::MyKnownDirectoryMyScript.ps1 and the referenced files were in the same directory as the script then life would be magic.
When the script is downloaded to the endpoint, it will be downloaded to C:WindowsIMECache – the folder containing the actual script and source files will be in a subfolder of IMECache that is a random GUID.
Alas, when deploying scripts using Endpoint Manager you have no idea of the random GUID subfolder containing the script and any accompanying source files essential to the deployment.
You need to discover where the script is running from, only then will you be able to reference the files for copy commands to work successfully. After all, copy commands work by copying from a known source location to a destination. How can this be achieved without knowing the source?
How We Used to Do Things
In the good old days, you would write a batch file and use %∼dp0 to reference the current location.
The %∼dp0 variable, when referenced within a Windows batch file, will expand to the drive letter and path of the batch file. This meant you would never need to know where the batch file was running from.
Ugly, right? Will it work? Sure, and there is certainly a place for the $MyInvocation automatic variable1 and it is capable of so much more; however, for your deployment needs there must surely be a more elegant solution.
A Better Way
Since PowerShell version 3, there is an easier method of determining where a PowerShell script is being executed from.
The automatic variable $PSScriptRoot contains the full path of the executing script's parent directory. It doesn’t get much easier than that.
You will learn how to use this automatic variable to reference files later in this chapter, but first, there needs to be an agreed method on how the files will be structured, and that’s next.
File Placement
At a minimum, you will always have two files somewhere in a directory that will make up the deployment package you will learn how to create later in the book: the PowerShell .ps1 script that will take care of the deployment and the application that will be deployed. Beyond that, there may be additional files required as part of the deployment, and deciding where to place these files inside the directory is a matter of personal preference. The only difference will be how you reference the files within a PowerShell deployment script.
Flat-File Placement
Advantages of Using a Flat-File Structure
Using this method for storing all files in a single directory can be a quick option when you are only deploying an application or have only a single file type for each file that needs deploying. (Having a single file type negates the need for filters in subsequent PowerShell code, and you will see an example of using a filter later in the book using the Copy-Item cmdlet.)
Structured File Placement
In a structured approach, you should leave the deployment script in the root directory. For every file type to be deployed, including the application itself, a subdirectory is created to contain the appropriate files.
The PowerShell DeploymentScript.ps1 in the root
Subdirectory named EXE to contain the application Setup.exe
Subdirectory named INI to contain the INI files
Subdirectory named JPG for the image files
Advantages of Using a Structured Approach
Not only is a structured approach more visually appealing (imagine if there were 50 or more other files to deploy), but there are also advantages to using this method.
For example, if you are copying all files in the JPG subdirectory to a destination, you can reference the source location as JPG*.* to copy everything contained within it. This simplifies the PowerShell code as you do not need to filter out the other file types when using the Copy-Item cmdlet. (Discussed later in the book.)
Whether all files are placed in a root directory or multiple subdirectories is an entirely personal preference. Just ensure the PowerShell deployment script or template you are using is always in the root location for ease, the rest doesn’t matter.
Referencing Files
Now that some thought has been given to where to place the files, and you know how to obtain the location that the script is being executed from, ($PSScriptRoot), it is time to learn how to reference the files from the PowerShell script.
First Things First
Whether you are using a deployment template or a custom PowerShell script, somewhere near the beginning of the script you should set the current working directory to the root path of where the script is being executed from.
PowerShell’s Set-Location is akin to the DOS CD (change directory) command. Having set the location to the current working directory of the script, it is now very easy to reference other files that are in the root directory.
Referencing Files in a Flat Structure
As the current script location has already been set (Set-Location $PSScriptRoot) all that is required to reference files is to precede the filename with . (period backslash).
. means the path is relative to the current directory.
For example, to reference an MSI called App1.msi you would use the following syntax in the PowerShell deployment script: .App1.msi
Referencing a text file is the same: .MyTextFile.txt
I should point out that you can also use the automatic variable itself to reference the files should you prefer to: $PSScriptRootApp1.msi or $PSScriptRootMyTxtFile.txt and, arguably, it may make for better code legibility.
Referencing Files in Subdirectories
To reference files that are more structured and organized is as simple. If you have changed to the working directory of the script execution path in your deployment script (Set-Location $PSScriptRoot) then you can reference the files using the following syntax: ".MySubdirectoryMy Text File.txt".
Alternatively, you could achieve the same thing using the automatic variable $PSScriptRoot using the following syntax: "$PSScriptRootMySubdirectoryMy Text File.txt".
Note the use of quotes in the above examples. While not necessary in all cases, if you have a space anywhere in the path then quotes are required. As a habit, you should always use quotes even if the file path does not contain spaces.
Using $PSScriptRoot when referencing files instead of . negates the requirement of adding Set-Location $PSScriptRoot in your PowerShell deployment script as it already contains the full path to where the script is being executed from.
If you have added the code Set-Location $PSScriptRoot at the start of your PowerShell deployment script, then the file would be referenced like this:
.ImagesWallpaper.jpg If you have not added the Set-Location code or you just want to anyway, you could reference the file using $PSScriptRoot because it already contains the full path of the script execution location. In this case, you can reference the file using the following syntax: $PSScriptRootImagesWallpaper.jpg.
To Me, To You, and Back Again
There may be a requirement where you must change the working directory from the script execution root path to an entirely new path by a different PowerShell provider.
You can do this by referencing the full path directly in the relevant PowerShell cmdlet.
Take the following scenario as an example:
The deployment script contains Set-Location $PSScriptRoot right at the very start of the script. The current working directory has now been set and the rest of the PowerShell code uses this to its advantage: various procedures are performed and files are referenced by beginning the path with . – for example, .SubDirectory1SubDirectory2image1.jpg.
Let’s say the script must now perform a lot of registry manipulation in the HKLM:SoftwareMyCompany and HKLM:SoftwareMyCompanyAttributes keys.
You could do this by specifying the full registry path in the cmdlets being used like so: New-ItemProperty -Path "HKLM:SoftwareMyCompany" ↩
-Name "EmpNo01" -Value 001 | Out-Null
Writing to the registry - a different location to $PSScriptRoot
Code Breakdown
- 1.
Line 1: The screen is cleared using Clear-Host.
- 2.
Line 3: The current working directory is set to the path that the script is being executed from.
- 3.
Line 5: Write-Host is used to display the working path which is C: emp.
- 4.
Lines 9–12: Various changes are made to the registry using the New-ItemProperty cmdlet. Note the -path parameter contains the full location of the registry key being manipulated.
- 5.
Line 16: The registry changes have been made and Write-Host is used to display that the working location has not changed. It is still in C: emp.
Now, there is nothing wrong with this method, but if you are changing location back and forth a lot in your script then there is another method that you should be aware of.
Push/Pop-Location
There are two PowerShell cmdlets that you can use to effortlessly change to a different location and back again: Push-Location and Pop-Location.
Push-Location
Push-Location is like Set-Location in that it will change to a new working directory. (Also like the DOS CD (change directory) command.)
The main and crucial difference is that it will remember where it came from. It “pushes” the current location to a location stack and if specified, will then change the current location to the location specified in the path. If the current location is C:Temp and you wish to change location to C:Windows the syntax is as follows: Push-Location C:Windows
When you are ready to return to the original location (C:Temp), you use the cmdlet: Pop-Location.
Pop-Location
The Pop-Location cmdlet changes the current location to the location most recently pushed onto the stack by using the Push-Location cmdlet. You do not need to add any parameters to Pop-Location to return from whence you came.
Let’s Try This Again
Changing to a new location using Push-Location now enables you to reference paths more easily using . (period backslash). You can push to locations all over the place knowing that with a simple pop you can easily revert to the location you just came from.
Using Push-Location and Pop-Location to change to a new working directory and back again
Code Breakdown
- 1.
Line 1: The screen is cleared using Clear-Host.
- 2.
Line 3: The current working directory is set to the path that the script is being executed from.
- 3.
Line 5: Write-Host is used to display the working path which is C: emp.
- 4.
Line 8: Push-Location is used to change the working directory to a registry location, HKLM:softwareMyCompany.
- 5.
Line 10: Write-Host is used to display the working path which is now HKLM:softwareMyCompany.
- 6.
Lines 14–17: Various changes are made to the registry using the New-ItemProperty cmdlet. Note the -path parameter now contains current path abbreviations (. and .) to reference the required registry locations of the registry keys being manipulated.
- 7.
Line 21: Pop-Location is used to return to the original location just came from.
- 8.
Line 23: The registry changes have been made and Write-Host is used to display that the working location has returned to C: emp.
Choosing to use Push-Location and Pop-Location over specifying a full path in your PowerShell code is probably a stylistic choice for the short scripts that you will be writing and there is no wrong or right way. If you find that you are hopping around all over the place in your code, then it might make more sense to use these two cmdlets.
I chose to include it in this book as it is something that I always use and have used in the sample Deployment Template script that you will learn about later in the book – and as my intention is for you to fully understand all aspects of the script, it made sense then, to explain it here.
Summary
A short but tough chapter this one. You learned how to find out where the deployed PowerShell script is being executed from and then the two different ways you could place your files needed for a deployment.
It was explained how to reference those files and then how to reference new locations that are not held by the $PSScriptRoot automatic variable.
Finally, you learned an alternative method of navigation between locations using the Push-Location and Pop-Location cmdlets.
In the next chapter, you will learn how to invoke the installation of the application itself.