When running a powershell script which changes a directory via cd (or set-location/push-location/etc.), the console that the script was run from also ends up in that directory.
Example:
script.ps1
cd c:\tmp
# do stuff
If I run this from c:\users\me, i end up in c:\tmp.
PS C:\users\me> .\script.ps1
PS C:\tmp> |
Now I know that I could use push-location and later do pop-location. However this wouldn't work if the script stopped somewhere in the middle (via Exit).
How can I solve this? Why doesn't the script have it's own location stack?
You could always use Try/Catch/Finally. Set the current directory path to a variable and then cd c:\tmp before the Try, and have the directory changed to variable in the Finally?
Example 1
$path = (Get-Item -Path ".\" -Verbose).FullName
cd c:\temp
Try
{
#Do stuff
#exit is fine to use
#this will output the directory it is in, just to show you it works
Write-Host (Get-Item -Path ".\" -Verbose).FullName
}
Catch [system.exception]
{
#error logging
}
Finally
{
cd $path
}
Example 2 using popd and pushd
pushd c:\temp
Try
{
#Do stuff
#exit is fine to use
#this will output the directory it is in, just to show you it works
Write-Host (Get-Item -Path ".\" -Verbose).FullName
}
Catch [system.exception]
{
#error logging
}
Finally
{
popd
}
I'd also recommend looking at what arco444 suggested, which is calling the powershell script via the -File parameter. Depending on the scenario that might work as an option.
Related
Let's say I have a simple powershell script:
# File: myscript.ps1
set-location C:\Windows
Then I invoke this script from the prompt as follows:
PS> Get-Location
C:\User\Dude
PS> .\myscript.ps1
PS> Get-Location
C:\Windows #!!!!!!!!!
I don't want my script to change the calling shells path... how to prevent this? Can I put something in the code to turn off exporting the PATH to the parent? or is there a way to somehow set it back to the original value before exiting the script, either on error or normal termination?
Rather than
Set-Location C:\Windows
Try using the Push-Location and Pop-Location in your script
#start of script
Push-Location C:\Windows
...
Pop-Location
#end of script
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/push-location?view=powershell-7.2
When to use Push-Location versus Set-Location?
# Run ScriptBlock from a Temporary Path
function better_with_path {
param(
[string]$newpath,
[ScriptBlock]$action
)
Push-Location $newpath
try
{
& $action
}
finally
{
Pop-Location
}
}
Okay so the script below is originally made using Batch and i converted it to PS. It works at the moment but is there better way to do this?
First we want to check is operating system 32 or 64 bit so we'll get the right installation path. Then we want to check is there old installation folder or not and if there is, the script should stop.
If there isn't that old installation folder, we'll create one and then import the registry file.
After that, we want to change drive H: to C:\Temp and then we'll install the msi-file. When the msi-file is installed, we want to check the installation path is the "program.exe" in the right place.
If everything is ok, we want to create folder for the GCTI files and then copy all the necessary files.
At the end of the script there's couple more file copying left and then we are done.
At the moment this script is in the same folder as the necessary installation files and when we use this to install the program, we need to copy the folder to the remote computer. I am planning to change this script a bit so that at first it asks on which computer we want to install this and then it copies all the files to the specific remote computer and then runs this script in remote computer.
#Let's check is OS 32 or 64 bit
$bit = "C:\Windows\syswow64\."
$isit64bit = Test-Path $bit
If ($isit64bit -eq $True) {$installpath = "C:\Program Files (x86)"}
Else {$installpath = "C:\Program Files"}
#Let's check is there old installation folder
$Program = $installpath+"\Program\"
$Programtest = Test-Path $Program
If ($Programtest -eq $false ) {Write-Host "None found, let's continue the installation"}
Else {Write-Host "Old installation folder found, remove files and try again" Exit}
# Create ODBC-connection in registry
Start-Process -FilePath Reg -ArgumentList import, ".\Progserver_ODBC.reg" -Wait -WindowStyle Minimized
#Let's check if previous action is ok
$registry = "HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI\Progserver\"
$registrycheck = Test-Path $registry
If ($registrycheck -eq $True) {Write-Host "Registrychange is ok"}
Else {Write-Host "Registrychange failed" Exit}
# Rename Drive "H:" C:\temp
New-PSDrive -Name "H" -PSProvider 'FileSystem' -Root C:\temp
# Install the msi
Start-Process -FilePath msiexec -ArgumentList /i, "Program-4.3.32.msi", /quiet -Wait
$install = "C:\Program Files (x86)\PathtoProgram.exe"
$installcheck = Test-Path $install
If ($installcheck -eq $True) {Write-Host "Installation succeeded"}
Else {Write-Host "Installation failed." Exit}
# Create GCTI's
$GCTI = "$installpath\PathToGCTI\"
If (Test-Path $GCTI) {Write-Host "GCTI folder already exists"}
Else {Write-Host "Create GCTI folder"} New-Item -ItemType Directory -Path $GCTI -Force
Copy-Item .\PathtoGCTI\* -Destination $GCTI -Recurse -Force
Write-Host "Copied GCTI-files"
# Copy program.ini ja vec.ini
Write-Host "Copying program.ini ja vec.ini"
Copy-Item .\PathToProgram.ini $installpath\PathToProgram.ini
Copy-Item .\PathToVec.ini $installpath\PathToVec.ini
# Change folder rights for the installation folder
cacls.exe $installpath\Program /T /E /G "All Users:C"
# Copy files from version 4.3.26
Copy-Item .\PathToProgram.exe $installpath\PathToProgram -Force
# Copy files
Copy-Item .\PathToFiles\* $installpath\PathToProgram\ -Force -Recurse
Set-ItemProperty $installpath\PathToProgram\graph\* -Name isreadonly $true
#Remove PSDrive
Remove-PSDrive -Name "H"
This is by no means exhaustive; just a selection of comments. You might be better submitting this to the Code Review StackExchange site.
General rules:
always used named parameters in PS functions
Your code formatting is important (carriage returns, tabbing/spacing, etc.)
e.g.
### Don't do this
Get-ChildItem "C:\temp"
### Instead do this
Get-ChildItem -Path "C:\temp"
Test if we're running a 64 bit OS and pick the appropriate Program Files folder
if ([environment]::Is64BitOperatingSystem) {
$installationPath = $env:ProgramFiles
}
else {
$installationPath = ${env:ProgramFiles(x86)}
}
When joining file paths, use Join-Path:
### So don't do this
$Program = $installpath+"\Program\"
### Instead do this
$Program = Join-Path -Path $installpath -ChildPath "Program"
When mapping your drive; be careful about scoping.
And probably best ensure you clean up after yourself, too! I have fallen foul of this in the past and it was a nightmare to clean up :-S
try {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
New-PSDrive -Name "H" -Root "C:\temp" -PSProvider FileSystem -Scope Script
### do your other stuff
catch {
throw $_.Exception
}
finally {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
}
Note the defensive programming (check if the drive exists already and remove it if it does!).
For bonus points you could pull the mapping code in to a separate function to avoid repetition (D.R.Y.)
Phew... I think that will do for now!
Good work :-)
Given this file
Set-Location C:\
If I run it
.\foo.ps1
It will change the directory in the script. However once the script is finished the parent console directory has also been changed. Can Set-Location be called in such a way as to affect only the running script?
try
{
Push-Location
Set-Location c:\
# your code here ...
}
finally
{
Pop-Location
}
Starting up notepad++ or many other GUI applications in Windows that will accepted fully qualified filenames of documents on the command line, but which do not accept them if they are not fully qualified, is often done in DOS/Windows batch files like this:
#echo off
start "notepad++" "C:\Program Files (x86)\Notepad++\notepad++.exe" %*
The above, if saved as "npp.cmd" will let you type "npp foo.txt" and it will work.
Note that without the npp.cmd, even typing out the full path to the exe, but not fully qualifying the file to edit doesn't work, like this:
"C:\Program Files (x86)\Notepad++\notepad++.exe" foo.txt
This however, DOES work:
"C:\Program Files (x86)\Notepad++\notepad++.exe" c:\users\warren\foo.txt
A way to easily work around this limitation is to make a batch file (.cmd) as shown at the top of this file. I'm learning PowerShell and trying to find the equivalent magic to the "start .... %*" incantation in the batchfile at the top. I believe it would have to be a 'powershell function'.
Here's what I have so far:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp $args }
The above is equivalent, in the end to simply an alias, because $args is really not equivalent to %*, in that it does not do parameter expansion. I think I need something like this:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp globexpand($args) }
globexpand is of course, a placeholder, for some kind of expansion/globbing routine, which I haven't been able to find yet in PowerShell.
try this:
new-item -path alias:nppapp -value "C:\Program Files (x86)\Notepad++\notepad++.exe"
function npp { nppapp (join-path -Path $pwd -ChildPath $args[0]) }
$pwd is an automatic variable with the current working path as value
Edit:
function npp {
if ($args[0] -match '.:\\.+')
{
nppapp $args[0]
}
else
{
nppapp (join-path -Path $pwd -ChildPath $args[0]) }
}
Is there a way of doing the following in PowerShell?
xcopy \\m1\C$\Online\*.config \\m2\C$\Config-Backup /s
I have tried this:
Copy-Item \\m1\C$\Online\* -Recurse -Destination \\m2\C$\Config-Backup -include *.config
But it does nothing, probably because there are no configuration files in the root. How do I do it?
If you would like to use native PowerShell (with a third party .NET module :P) and also don't want to let long file paths (> 255 characters) halt the copy, you can use this:
# Import AlphaFS .NET module - http://alphafs.codeplex.com/
Import-Module C:\Path\To\AlphaFS\DLL\AlphaFS.dll
# Variables
$SourcePath = "C:\Temp"
$DestPath = "C:\Test"
# RecursePath function.
Function RecursePath([string]$SourcePath, [string]$DestPath){
# for each subdirectory in the current directory..
[Alphaleonis.Win32.Filesystem.Directory]::GetDirectories($SourcePath) | % {
$ShortDirectory = $_
$LongDirectory = [Alphaleonis.Win32.Filesystem.Path]::GetLongPath($ShortDirectory)
# Create the directory on the destination path.
[Alphaleonis.Win32.Filesystem.Directory]::CreateDirectory($LongDirectory.Replace($SourcePath, $DestPath))
# For each file in the current directory..
[Alphaleonis.Win32.Filesystem.Directory]::GetFiles($ShortDirectory) | % {
$ShortFile = $_
$LongFile = [Alphaleonis.Win32.Filesystem.Path]::GetLongPath($ShortFile)
# Copy the file to the destination path.
[Alphaleonis.Win32.Filesystem.File]::Copy($LongFile, $LongFile.Replace($SourcePath, $DestPath), $true)
}
# Loop.
RecursePath $ShortDirectory $DestPath
}
}
# Execute!
RecursePath $SourcePath $DestPath
Please note this code was stripped out of a much larger project of mine, but I gave it a quick test and it seems to work. Hope this helps!
Start-Process xcopy "\\m1\C$\Online\*.config \\m2\C$\Config-Backup /s" -NoNewWindow
:P
The new AlphaFS 2.0 makes this really easy.
Example: Copy a directory recursively
# Set copy options.
PS C:\> $copyOptions = [Alphaleonis.Win32.Filesystem.CopyOptions]::FailIfExists
# Set source and destination directories.
PS C:\> $source = 'C:\sourceDir'
PS C:\> $destination = 'C:\destinationDir'
# Copy directory recursively.
PS C:\> [Alphaleonis.Win32.Filesystem.Directory]::Copy($source, $destination, $copyOptions)
AlphaFS on GitHub
Look into robocopy. It's not a native PowerShell command, but I call it from PowerShell scripts all the time. Works similarly to xcopy only it's way more powerful.