I have the following code in a powershell script. And I have file 7za.exe in the same directory of the script.
param($sql)
$temp = [System.IO.Path]::GetTempFileName()
Invoke-Sqlcmd -ServerInstance sqlserver1 $sql | ConvertTo-Csv > $temp
.\7za.exe a '$temp.zip' '$temp'
However, it got the following error. What's the right approach to execute an executable in the same folder right after invoke-sqlcmd?
SQL Server PowerShell provider error: Path SQLSERVER:\7za.exe does not exist. Please specify a valid path.
+ CategoryInfo : OperationStopped: (:) [], GenericProviderException
+ FullyQualifiedErrorId : Microsoft.SqlServer.Management.PowerShell.GenericProviderException
+ PSComputerName : localhost
If you look at the path in the error message, it is "SQLSERVER:\7za.exe" -- so the current working directory for the instance of PowerShell running this script (which you can get using the automatic variable $pwd) is SQLSERVER:\, which is the PSDrive created by the SQL module\snap-in that must be loaded to run the Invoke-SqlCmd cmdlet.
PowerShell scripts do NOT use their current location as the current working directory by default. In PowerShell version 3 and later, however, you can get the script's directory using the automatic variable $PSScriptRoot. In earlier versions, you can generate $PSScriptRoot yourself with:
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Definition
Then you could use this variable in your script like so:
& (Join-Path $PSScriptRoot '7za.exe') "$temp.zip" "$temp"
Note that I used double quotes around the $temp variable, so that PowerShell would automatically expand the variable into the correct name.
#jbsmith is right about what path to use. Contributing to this issue is that the SQLPS module changes the current directory to SQLSERVER: when it is imported.
Related
Here are 2 of my scripts. The first shows a list of directories with more than one file. The second is just a test because I never used $PSScriptRoot before.
Get-ChildItem -Path $PSScriptRoot -Directory -Name -Recurse |
Write-Output
Where-Object { (Get-ChildItem $_ -File).Count -ge 2 }|
ForEach-Object { ".\$_"} |
Invoke-Item
# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost")
{
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}
Write-Output $PSScriptRoot
# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost")
{
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}
In the beginning, I did not have the -Path $PSScriptRoot and I ran my scripts using "Run with Powershell" in the context menu. This way the scripts seemed to use the scripts directory as the working directory. When I changed the default app to PowerShell, it started using PowerShell's install directory as the working directory. So I added -Path $PSScriptRoot and now the first script does not work; it opens a console and closes it right away. The second script works just fine.
Also, The first script still runs correctly if I "Run with Powershell" using the context menu.
What Am I doing wrong in the first script?
I am guessing that the path to your script has spaces in it. The problem has to do with the way windows passes the file argument to the application and the way the path to the script is being parsed.
When you run your script with "Run with Powershell", the following command is sent to the shell:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'C:\Temp\with spaces\scriptRoot.ps1'"
You'll notice that the command is wrapped in quotes. Which means that the shell will pass the quoted part as an argument to the powershell executable and effectively strips the outer " quotes. So powershell sees this as a command:
if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'C:\Temp\with spaces\scriptRoot.ps1'
Now you'll notice that the path to the script is still wrapped in single quotes and everything still works even though the path has spaces.
When you run the script as "Open with ..." powershell, windows (the explorer shell) will just pass the path to the script wrapped in quotes. It looks like this:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "C:\Temp\with spaces\scriptRoot.ps1"
If I open an command prompt and try to run the above command, I get the following:
#>"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "C:\Temp\with spaces\scriptRoot.ps1"
C:\Temp\with : The term 'C:\Temp\with' is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ C:\Temp\with spaces\scriptRoot.ps1
+ ~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Temp\with:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
That's because powershell itself never sees the outer quotes. If I add an extra layer of '' it works:
#>"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "'C:\Temp\with spaces\scriptRoot.ps1'"
C:\Temp\with spaces\scriptRoot.ps1
Unfortunately, I'm not sure how to fix this other than getting rid of the spaces in your path.
I am trying to streamline how I execute some scripts I wrote by setting up a function and alias to run them. I currently have functions to change my directory to where the scripts need to be run, but when I try to run the script itself I get the following error:
C:\Users\me\Desktop\BoB : The term 'C:\Users\me\Desktop\BoB' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
again.
At line:1 char:1
+ C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\jteit\Desktop\BoB:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The function is:
function run-scanner { "& 'C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1'" | Invoke-Expression }
I've tried a few variations based on other answers I've found, but I keep getting the same error. I would prefer to not remove the space on the path because other scripts use it successfully. Running the script from the ISE gives me no problems.
Ideally I would like the function to also allow me to have the script run on the folders I would like without changing the working directory (each script works on a particular set of files that are in a static location but some of them use $PWD to get the folders in the location).
For example in my $profile file I have this function: function go-to-temp {cd "C:\Users\me\Desktop\Bob Tools\To be Formatted\Temp"} which I run before I execute the above script. I would like them rolled into a single command without my working directory changing (which would render the go-to-temp function redundant.
What am I doing wrong?
There is no reason to run your script through Invoke-Expression.
Unless your script relies on $PWD, then you should be able to execute it with the call operator: &. As the other poster mentioned, you can use dot-sourcing (.) if you need the variables the script generates, but this will import all global objects (aliases, variables, functions) to your current scope. If it does rely on $PWD, you can utilize Start-Process with -WorkingDirectory to avoid changing where you're at.
function Start-Scanner {
& "$HOME\Desktop\BoB Tools\folderScannerV0.4.ps1"
}
or
function Start-Scanner {
$startArgs = #{
FilePath = "$PSHOME\powershell.exe"
ArgumentList = '-File', "`"$HOME\Desktop\BoB Tools\folderScannerV0.4.ps1`""
WorkingDirectory = "$HOME\Desktop\BoB Tools"
NoNewWindow = $true
Wait = $true
}
Start-Process #startArgs
}
You can just use dot-sourcing for this:
function run-scanner { . 'C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1' }
I've currently got a script that:
Loops through some files
Checks the name of the file
If there is no directory for the file it will create one
Moves the file into the directory
The moving logic works fine. However if a directory does not exist I am given this error (the path is valid, except it does not exist)
C:\Users\User\Documents\Directory\FileName : The term 'C:\Users\User\Documents\
Directory\FileName' is not recognized as the name of a cmdlet, function, script
file, or operable program. Check the spelling of the name, or if a path was
included, verify that the path is correct and try again.
At line:1 char:1
C:\Users\User\Documents\Directory\FileName
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\User\Documents\Directory\FileName) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The curious part is that it does actually create the folder - however it crashes my script.
Here's the problem part of the script
function CanCreate($dir) {
return !(Test-Path $dir)
}
if (CanCreate($fullDestinationPath)) {
New-Item $fullDestinationPath -ItemType Directory
}
md/mkdir behave differently to New-Item in that they do crash the script, however New-Item prints the error and continues (script seems to finish its job).
Edit:
The issue seems to stem from the fact that I am calling the script from another script.
$ScriptPath = "C:\Powershell Scripts\script.ps1"
& $ScriptPath | Invoke-Expression
The issue was with the fact that I was using | Invoke-Expression to trigger the script.
Simply calling & $ScriptPath, omitting the | Invoke-Expression, was enough to trigger the script.
Thanks everyone.
I wrote a script in PowerShell and I am having various success calling Image Magick's montage.exe on different computers. The computer on which I wrote the script has no problem executing the 'montage' command, however on another computer with IM Installed the script errors out:
montage : The term 'montage' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At \\Server\Contact_Sheet_Local.ps1:51 char:9
+ montage -verbose -label %t -pointsize 20 -background '#FFFFFF ...
+ ~~~~~~~
+ CategoryInfo : ObjectNotFound: (montage:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I have tried using montage.exe and even tried the entire path C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe. Additionally I tried setting the directory first:
Set-Location -Path 'C:\Program Files\ImageMagick-7.0.3-Q16'
montage...
Each time on a particular computer this fails. I have tried with IM versions 7.0.3-Q16 and 6.9.1-6-Q8 both x64 as both computers are x64
In the past, I have created scripts in .bat that use ImageMagick and I had to define the full path to the .exe as I mentioned above. But this doesn't seem to help in PowerShell.
Does anyone have any advice or experience with this problem?
If your path has spaces, it will fail if you're just trying to execute based on that. You'll want to utilize the dot operator
$Exe = 'C:\Program Files\ImageMagick-6.9.1-6-Q8\montage.exe'
If (-not (Test-Path -Path $Exe))
{
$Exe = 'C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe'
}
. $Exe -arg1 -etc
PowerShell does not execute programs in the current directory by default. If you want to run an executable that's sitting in the current directory, prefix the executable's name with .\ or ./. Example:
Set-Location "C:\Program Files\ImageMagick-7.0.3-Q16"
.\montage.exe ...
If you have an executable's name in a string or string variable and you want to execute it, you can do so using the & (call or invocation) operator:
& "C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe" ...
If you specify a path and filename that doesn't contain spaces, the & operator isn't required; example:
C:\ImageMagick\montage.exe ...
You could also write it this way:
& C:\ImageMagick\montage.exe ...
If you have an executable's filename in a string variable and you want to execute it, use &; example:
$execName = "C:\Program Files\ImageMagick-7.0.3-Q16\montage.exe"
& $execName ...
In PowerShell I can echo the value of %TEMP% using the command $Env:TEMP. Here is the output on my machine:
PS> $Env:temp
C:\Users\IAIN~1.COR\AppData\Local\Temp
When I try to change to the directory using the cd command, I receive this error:
PS> cd $Env:temp
Set-Location : An object at the specified path C:\Users\IAIN~1.COR does not exist.
At line:1 char:3
+ cd <<<< $Env:temp
+ CategoryInfo : InvalidArgument: (:) [Set-Location], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.SetLocationCommand
I suspect that PowerShell is interpreting the 8.3 file name literally. The long file name of the directory is C:\Users\iain.CORP\AppData\Local\Temp. When I try cd C:\Users\Iain.CORP\AppData\Local\Temp, the directory changes successfully.
How can I open the path in $Env:TEMP using PowerShell? Do I have to have the long file name first?
You don't need to access the %TEMP% environment variable directly.
.NET provides a GetTempPath method as a more general solution.
$TempDir = [System.IO.Path]::GetTempPath()
cd $TempDir
On my machine, this changes to the directory C:\Users\Iain.CORP\AppData\Local\Temp.
Remarks from the documentation:
This method checks for the existence of environment variables in the
following order and uses the first path found:
The path specified by the TMP environment variable.
The path specified by the TEMP environment variable.
The path specified by the USERPROFILE environment variable.
The Windows directory.
Thanks to Joe Angley for sharing the technique.
Open $env:temp by first resolving its fullname like this:
cd (gi $env:temp).fullname
cd $env:temp
This worked for me.