In a Powershell script, some commands don't recognize PS Drive - powershell

The following script maps a network drive with the new-psdrive command.
It uses get-content to load the contents of an XML file to a variable.
Truncated lines modify the XML object.
Then it uses the XML save method to save the file.
The script gives the error "Exception calling "Save" with "1" argument(s): "Could not find a part of the path 'M:\folder3\pems.xml'."
clear
Remove-PSDrive M
New-PSDrive -Name M -Root "\\server1\share\folder1\folder2\" -PSProvider FileSystem
$uncpath = "\\server1\share\folder1\folder2\"
$xmlfileDrive = "M:\folder3\pems.xml"
$xmlfileUNC = $uncpath+"folder3\pems.xml"
Get-ChildItem M:
Get-ChildItem $uncpath
#####Truncated code that manipulates $xmlfile
#####Calls outside executables that don't support UNC paths.
[xml]$scheme = Get-Content $xmlfileDrive
$scheme.Save($xmlfileDrive)
If I change the last line from
$scheme.Save($xmlfileDrive)
to
$scheme.Save($xmlfileUNC)
No error appears, and the script functions properly.
Note that the "Get-Content" command on the second to last line works properly regardless of which variable I use. And the "Get-ChildItem" commands give identical results.
Why would some commands recognize the mapped drive and others not?
I've tested this in both Powershell 5 and 7.

I fixed the problem by adding "-persist" to the "New-PSDrive" command. After that the save() method and all my external calls to executables recognized the drive.
As Mathias R Jesson stated, any commands that aren't native Powershell commands won't recognize the PSDrive object (unless -persist) is used.

Related

How to execute a string in a variable in powershell

I have the following string
"C:\ProgramData\Package
Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe"
/uninstall
I need to execute the above string as below
First-line
cd C:\ProgramData\Package Cache\{6b95042e-f763-4850-9136-d004dd0d0a9b}
The second line (note there is no exe)
AzInfoProtection /uninstall
Variables are generally executed like below in PowerShell
Invoke-Expression $cmd
But how to split the above string into multiple lines for execution. Then I need to remove the quote and then exe.
It's a bit hard to understand the ask here but I think I follow. Let me know if I'm off base or misunderstanding what you're trying to do.
$commandString = '"C:\ProgramData\Package Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe" /uninstall'
# Get command parent directory
if( $commandString -match '^".*?"' ) {
$runInDir = Split-Path -Parent $Matches[0]
}
# Change directories (use the location stack for easy traversal)
Push-Location $runInDir
# Run program
Invoke-Expression $commandString
# Change back to previous directory
Pop-Location
This works by checking if the string starts with a quote-enclosed string (escaped quotes should not need to be handled within filepaths), and if so gets the first match from the $Matches object. $Matches is an automatic variable which is populated whenever you get a $True result using the [-match operator][1]. With the command path extracted, we use Split-Path to get the parent container relative to the filepath.
Then use Push-Location to change directories. Push-Location works like Set-Location (aliased to cd) except it tracks the directories you leave and enter as a stack. Its sibling cmdlet Pop-Location is used further on to return to the previous location.
Finally, we use Invoke-Expression to run your command. After this completes use Pop-Location to return to the previous directory. Keep the following in mind:
You should take note that the use of Invoke-Expression is often implemented insecurely, and so you should consider heeding the warning on the documentation I've linked to and consider parameterizing your command if your $commandString is actually populated from a generated file, provided by a parameter, or another other outside source.
Note: You mentioned this in your question:
The second line (note there is no exe)
Windows doesn't care if you omit the extension for executable types when executing them. You can run AzInfoProtection.exe with or without the .exe at the end. So unless I'm missing something this detail doesn't have any bearing on how this code works.
To run the string you can pipe it to cmd to run it using:
$commandString | cmd

Powershell $PSScriptRoot empty when using Invoke-Command

When running a .ps1 on a remote computer using Invoke-Command, $PSScriptRoot is empty on the remote computer.
Is this normal? How can one find the .ps1 path when using Invoke-Command?
Further, other methods, such as through $MyInvocation are also empty.
Is this normal? How can one find the .ps1 path when using Invoke-Command?
There is no script file involved on the remote computer when you call
Invoke-Command -ComputerName $someComputer -FilePath c:\path\to\some\script.ps1
What PowerShell does in this case is to send the contents (parsed source code)[1] of the caller-local script file to the remote machine and executes it there.
Therefore, $PSScriptRoot (the directory in which a script file resides) doesn't apply and returns the empty string.
The same applies to other script-file-related automatic variables, such as $PSCommandPath and $MyInvocation.MyCommand.Name (see about_Automatic_Variables).
To put it differently: When the script file's code executes remotely, it doesn't know anything about the file it came from on the caller's side.
Thus, the only information available you there is $PWD, i.e., the current (working) location (directory).
[1] From the docs: "When you use this [the -FilePath] parameter, PowerShell converts the contents of the specified script file to a script block, transmits the script block to the remote computer, and runs it on the remote computer."

Set current location to match the Drive letter same as the running script's drive

In PowerShell, how to dynamically read the Drive (letter) where running Script resides then set that Drive as current location so that:
all relative paths written in the script would use (and create files/folder-structure) in that drive, also,
after script-execution finishes, the PS-Command-Prompt would also show that Drive (where the running script resides):
For example, before execution, if the current location is C: drive, i.e. the PowerShell command prompt is showing "C:\" and my script resides in "D:\ps_scripts\test.ps1", all relative paths in the script should use (and create files/folder) in the D: drive.
Basically I'm developing an installer-script that should install the app in the Drive where the script is stored (i.e. it has to be "Relative" as opposed to "Absolute/Fixed" drive).
TIA
I wrote following script which reads the drive-letter (such as C: drive) where currently running script resides, then sets current location (matching the same drive):
Write-host ('$PSScriptRoot: ' + $PSScriptRoot) #Checking full path of own running script.
$drive = ((Get-Item $PSScriptRoot).PSDrive.Name) #Getting drive letter of running script.
Set-Location -Path "$($drive):\" #Setting Disk-Drive on PS-Cmd-Prompt.
Note: I was getting error at the last line, because I had not enclosed the $drive variable within $(), which evaluates the expression, as soon I did this, Voila!!!
Hope this helps others.

Error in batch file due to space

I am having trouble with a seemingly simple script I have which essentially copies an item from the user's PC and places it on another computer; however, the destination file path contains a space in it. I have tried multiple methods of correcting this issue (some I don't completely understand) from using double quotation marks around the string to forcing it to run powershell.
To give a very brief precursor to this situation, I must add that I initially created this script using Powershell on Windows 10 and I should also add that it works completely fine in Powershell, just not as a .bat. I understand there may be some differences in the languages or what is interpreted through the programs.
Here is the string in the question:
Copy-Item $ENV:USERPROFILE\Desktop\VAST.accdb -destination "\\PRECDP19670\C$\Users\WAKE\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" -Force -PassThru -Verbose
The destination contains the space in the filepath.
Any help is appreciated!
As Bill mentioned, your BAT file needs to call PowerShell as an executable.
PowerShell.exe -command 'Copy-Item $ENV:USERPROFILE\Desktop\VAST.accdb -destination "\\PRECDP19670\C$\Users\WAKE\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" -Force -PassThru -Verbose

PowerShell: Run command from script's directory

I have a PowerShell script that does some stuff using the script’s current directory. So when inside that directory, running .\script.ps1 works correctly.
Now I want to call that script from a different directory without changing the referencing directory of the script. So I want to call ..\..\dir\script.ps1 and still want that script to behave as it was called from inside its directory.
How do I do that, or how do I modify a script so it can run from any directory?
Do you mean you want the script's own path so you can reference a file next to the script? Try this:
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "My directory is $dir"
You can get a lot of info from $MyInvocation and its properties.
If you want to reference a file in the current working directory, you can use Resolve-Path or Get-ChildItem:
$filepath = Resolve-Path "somefile.txt"
EDIT (based on comment from OP):
# temporarily change to the correct folder
Push-Location $dir
# do stuff, call ant, etc
# now back to previous directory
Pop-Location
There's probably other ways of achieving something similar using Invoke-Command as well.
There are answers with big number of votes, but when I read your question, I thought you wanted to know the directory where the script is, not that where the script is running. You can get the information with powershell's auto variables
$PSScriptRoot # the directory where the script exists, not the
# target directory the script is running in
$PSCommandPath # the full path of the script
For example, I have a $profile script that finds a Visual Studio solution file and starts it. I wanted to store the full path, once a solution file is started. But I wanted to save the file where the original script exists. So I used $PsScriptRoot.
If you're calling native apps, you need to worry about [Environment]::CurrentDirectory not about PowerShell's $PWD current directory. For various reasons, PowerShell does not set the process' current working directory when you Set-Location or Push-Location, so you need to make sure you do so if you're running applications (or cmdlets) that expect it to be set.
In a script, you can do this:
$CWD = [Environment]::CurrentDirectory
Push-Location $MyInvocation.MyCommand.Path
[Environment]::CurrentDirectory = $PWD
## Your script code calling a native executable
Pop-Location
# Consider whether you really want to set it back:
# What if another runspace has set it in-between calls?
[Environment]::CurrentDirectory = $CWD
There's no foolproof alternative to this. Many of us put a line in our prompt function to set [Environment]::CurrentDirectory ... but that doesn't help you when you're changing the location within a script.
Two notes about the reason why this is not set by PowerShell automatically:
PowerShell can be multi-threaded. You can have multiple Runspaces (see RunspacePool, and the PSThreadJob module) running simultaneously withinin a single process. Each runspace has it's own $PWD present working directory, but there's only one process, and only one Environment.
Even when you're single-threaded, $PWD isn't always a legal CurrentDirectory (you might CD into the registry provider for instance).
If you want to put it into your prompt (which would only run in the main runspace, single-threaded), you need to use:
[Environment]::CurrentDirectory = Get-Location -PSProvider FileSystem
This would work fine.
Push-Location $PSScriptRoot
Write-Host CurrentDirectory $CurDir
I often used the following code to import a module which sit under the same directory as the running script. It will first get the directory from which powershell is running
$currentPath=Split-Path ((Get-Variable
MyInvocation -Scope
0).Value).MyCommand.Path
import-module "$currentPath\sqlps.ps1"
I made a one-liner out of #JohnL's solution:
$MyInvocation.MyCommand.Path | Split-Path | Push-Location
Well I was looking for solution for this for a while, without any scripts just from CLI. This is how I do it xD:
Navigate to folder from which you want to run script (important thing is that you have tab completions)
..\..\dir
Now surround location with double quotes, and inside them add cd, so we could invoke another instance of powershell.
"cd ..\..\dir"
Add another command to run script separated by ;, with is a command separator in powershell
"cd ..\..\dir\; script.ps1"
Finally Run it with another instance of powershell
start powershell "cd..\..\dir\; script.ps1"
This will open new powershell window, go to ..\..\dir, run script.ps1 and close window.
Note that ";" just separates commands, like you typed them one by one, if first fails second will run and next after, and next after... If you wanna keep new powershell window open you add -noexit in passed command . Note that I first navigate to desired folder so I could use tab completions (you couldn't in double quotes).
start powershell "-noexit cd..\..\dir\; script.ps1"
Use double quotes "" so you could pass directories with spaces in names e.g.,
start powershell "-noexit cd '..\..\my dir'; script.ps1"