Powershell warning and error handling - windows

The following code I am writing to replace a batch script which I have below.
$Script:srcpath = ((Get-Location).Path)
$Script:configure = "$Script:srcpath\qtbase\configure.bat"
if (Get-Item "$Script:srcpath\qtbase\configure.bat" -WarningAction (Write-Warning "$Script:configure not found. Did you forget to run 'init-repository'?")) {
continue
}
I am try to rewrite the qt configure batch script:
set "srcpath=%~dp0"
set "configure=%srcpath%qtbase\configure.bat"
if not exist "%configure%" (
echo %configure% not found. Did you forget to run "init-repository"? >&2
exit /b 1
)
if not exist qtbase mkdir qtbase || exit /b 1
echo + cd qtbase
cd qtbase || exit /b 1
echo + %configure% -top-level %*
call %configure% -top-level %*
set err=%errorlevel%
cd ..
exit /b %err%
The error I am getting in PowerShell is the following:
Get-Item : Cannot bind parameter 'WarningAction' to the target. Exception setting
"WarningAction": "Object reference not set to an instance of an object."
At line:4 char:67
+ ... rningAction (Write-Warning "$Script:configure not found. Did you forg ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (:) [Get-Item], ParameterBindingException
+ FullyQualifiedErrorId : ParameterBindingFailed,Microsoft.PowerShell.Commands.GetItemCommand
The problem is the error being thrown is erroneous because the warning am calling is what should take its place to tell people that the item doesn't exist. so run "init-repository".
There isn't a good, "if not exist" in PowerShell.
Okay, there is, but it looks like this:
catch [System.Management.Automation.ItemNotFoundException]
which I am having problems with getting to work.
Why am I doing this before someone asks is because I feel that Microsoft will phase out CMD some time it's good to have updated scripts.

Why it's not working
WarningAction does not work like that.
From the about_CommonParameters documentation:
Determines how the cmdlet responds to a warning from the command. "Continue" is the default value. This parameter works only when the command generates a warning message. For example, this parameter works when a command contains the Write-Warning cmdlet.
So, essentially, the value of WarningAction is Continue by default and can be set to Inquire, SilentlyContinue or Stop. The value it is set to determine what action is taken if the Get-item command throws a warning, not what warning to write if Get-item throws a warning.
You can change the preference variable $WarningPreference to set the WarningAction within the current scope, or precede by a scope modifier.
How to make it work
Test-Path
I second Richard's comment to use Test-Path. This will return True or False, depending on whether it finds the file.
if (-not (Test-Path -Path "$Script:srcpath\qtbase\configure.bat")){
Write-Warning 'Does not exist!'
# do other stuff
continue
}else{
Get-Item $configure
}
try/catch
You could try to catch the exception thrown by Get-Item directly in a try/catch. In a similar way to WarningAction, there is ErrorAction which determines what to do if an error is thrown. Terminating error is required, so ErrorAction is set to Stop.
try{
Get-Item $configure -ErrorAction Stop
}catch [System.Management.Automation.ItemNotFoundException]{
Write-Output "Item not found"
# do other stuff
}catch{
Write-Output "Some other error"
$Error[0] # prints last error
}

Related

Logging the current executing command within the Action Block for Set-PSBreakpoint

I am attaching a command breakpoint for every "Get*" in my powershell session. This command breakpoint involves calling an Action which invokes Write-Host "We are trapped in this code". As you can see below
Eg.
PS /Users/test> Set-PSBreakpoint -Command "Get*" -Action {
>> Write-Host "We are trapped in this code"
>> }
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
1 Get* …
PS /Users/test> Get-Runspace
We are trapped in this code
Id Name ComputerName Type State Availability
-- ---- ------------ ---- ----- ------------
1 Runspace1 localhost Local Opened Busy
PS /Users/test>
Now instead of printing
We are trapped in this code
we want to print
We are trapped in this code: Get-Runspace
In this specifically we want to print the command ie. Get-Runspace on which breakpoint was applied.
Is there any way to get the hold of the Executing Command within the ScriptBlock.
PS: We have tried $MyInvocation.MyCommand.Name within the script block and various other methods but all of them return null.
Assuming that you're interested in the command line that triggered the breakpoint, use $MyInvocation.Line:
Set-PSBreakpoint -Command "Get-conten*" -Action {
Write-Host "We are trapped in this code: $($MyInvocation.Line)"
}
If you want to inspect the specific values of the automatic $MyInvocation variable (which is an instance of type System.Management.Automation.InvocationInfo), pipe it to the Out-Host cmdlet:
Set-PSBreakpoint -Command "Get-conten*" -Action {
$MyInvocation | Out-Host
}

How to push a command to the function parameters from the command line arguments?

I want to write a function that runs a given command and outputs a given message if there are errors when running this command. Command and message are passed to the function as an arguments.
Actually, this small script is for the project "A Continuous Integration System" from the series "50 Lines or Less". Here in a project description, all shell scripts are written in bash but i have Windows and i want rewrite all shell scripts on powershell, for some practice.
This is code that i have now:
function rof{
param(
[string] $msg,
[scriptblock] $cmd
)
try {
Invoke-Command $cmd
}
catch {
$msg
}
}
I expected that when i run this function like
rof -msg "some error :(" -cmd {git log}
that command in {} will be invoked or "some error" will outputs if, for example there are no git repository.
But function outputs
git log
What am i doing wrong? I've never written in powershell before. Help me please. Thank you!
Treat PowerShell as just another shell, you don't need to use Invoke-Command to call a command. Just call it as is, since you have to pass the arguments of the command, using Invoke-Expression will be the best option here.
function rof{
param(
[string] $msg,
[string] $cmd
)
try {
Invoke-Expression $cmd
}
catch {
$msg
}
}
The scriptblock was successfully invoked from PowerShell's point of view, meaning there is no terminating error which triggers the catch. What you can do instead is checking the $LASTEXITCODE variable, which will contain the exit code from your external command.
function rof {
param(
[string] $msg,
[scriptblock] $cmd
)
& $cmd
if ($LASTEXITCODE -ne 0) {
$msg
$LASTEXITCODE
}
}
Example:
rof -msg "test" -cmd {cmd.exe /c exit 123}
test
123
However, let me add that this proxy function sounds like a bad idea. I don't know your exact use-case, but this will just invoke anything (very unsecure in a constrained PowerShell) and always returns a message that is defined by the caller. But how would the caller know, what the error might be...?

How to stop power shell script on error?

I have bellow script
$ErrorActionPreference = "Stop";
while($true) {
try {
Write-Host "Step 1";
Dir C:\arts #Error
Write-Host "Step 2";
exit 0
break;
}
catch {
"Error in " + $_.InvocationInfo.ScriptName + " at line: " + $_.InvocationInfo.ScriptLineNumber + ", offset: " + $_.InvocationInfo.OffsetInLine + ".";
$Error
exit 1
break;
}
}
It stops on Dir C:\arts line and that is good for me. As I understood it happens cos I have line $ErrorActionPreference = "Stop"; at the beginning.
I also have some docker params
Param(
[Parameter(Mandatory=$True,ParameterSetName="Compose")]
[switch]$Compose,
[Parameter(Mandatory=$True,ParameterSetName="ComposeForDebug")]
[switch]$ComposeForDebug,
[Parameter(Mandatory=$True,ParameterSetName="StartDebugging")]
[switch]$StartDebugging,
[Parameter(Mandatory=$True,ParameterSetName="Build")]
[switch]$Build,
[Parameter(Mandatory=$True,ParameterSetName="Clean")]
[switch]$Clean,
[parameter(ParameterSetName="Compose")]
[Parameter(ParameterSetName="ComposeForDebug")]
[parameter(ParameterSetName="Build")]
[parameter(ParameterSetName="Clean")]
[ValidateNotNullOrEmpty()]
[String]$Environment = "Debug"
)
If I put $ErrorActionPreference = "Stop" line before docker params I will have error Cannot convert value "System.String" to type "System.Management.Automation.SwitchParameter". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.
In case if I put $ErrorActionPreference = "Stop"; line after docker params, script is continued to run and that is not that I want.
I do not know what I need to do here, so I will be grateful for any help
$ErrorActionPreference doesn't work with command line utilities like docker as they don't throw exceptions in PowerShell. You would have to use returncode/errorlevel or parse the output to handle those type of errors. Useful automatic variables:
$?
Contains the execution status of the last operation. It contains
TRUE if the last operation succeeded and FALSE if it failed.
$LastExitCode
Contains the exit code of the last Windows-based program that was run. Same as %errorlevel% in cmd.
If you detect an error, you can throw an exception to stop the script or use something like exit to stop the script. Example:
function Test-Error {
$ErrorActionPreference = "Stop"
Write-Host Before
ping -n 1 123.123.123.123
#If last command was not successfull.
#You can also have checked $lastexitcode, output etc.
if($? -eq $false) {
#Throw terminating error
#throw "Error"
#Or since we've chosen to stop on non-terminating errors, we could use:
Write-Error -ErrorId $LASTEXITCODE -Message "Ping failed"
}
Write-Host After
}
Test-Error
Output:
Before
Pinging 123.123.123.123 with 32 bytes of data:
Request timed out.
Ping statistics for 123.123.123.123:
Packets: Sent = 1, Received = 0, Lost = 1 (100% loss),
Test-Error : Ping failed
At line:22 char:1
+ Test-Error
+ ~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : 1,Test-Error
If you're creating a advanced function, you could set the default ErrorAction for the scope of the cmdlet like this:
function Test-Error {
[CmdLetBinding()]
param(
$Name = "World"
)
#If -ErrorAction is not specified by the user, use Stop for the scope of the function
if(-not $MyInvocation.BoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" }
"Hello $Name ! My ErrorAction is: $ErrorActionPreference"
}
PS > $ErrorActionPreference
Continue
PS > Test-Error -ErrorAction Ignore
Hello World ! My ErrorAction is: Ignore
PS > Test-Error
Hello World ! My ErrorAction is: Stop

Multiple statements using && [duplicate]

This question already has answers here:
Can I get "&&" or "-and" to work in PowerShell?
(13 answers)
Closed 9 years ago.
What would be the equivalent statement of CMD's:
dir && cd ..
in Powershell?
I tried:
dir -and cd ..
but it throws error:
Get-ChildItem : A parameter cannot be found that matches parameter name
'and'.
At line:1 char:5
+ dir -and (cd ..)
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell
.Commands.GetChildItemCommand
There is not a direct equivalent in PowerShell of cmd.exe && which means "only execute right-hand side if the left-hand side succeeds." However you could write a short function to do the equivalent:
function IfTrue([ScriptBlock] $testExpression, [ScriptBlock] $runExpression) {
if ( & $testExpression ) { & $runExpression }
}
For example:
IfTrue { get-childitem "fileThatExists.txt" -ea SilentlyContinue } { "File exists..." }
If you want for the $testExpression to produce output, the IfTrue function can be written as follows:
function IfTrue([ScriptBlock] $testExpression, [ScriptBlock] $runExpression) {
& $testExpression
if ( $? ) { & $runExpression }
}
Bill
How about this?
dir; if ($?) {cd ..}
Running get-help about_automatic_variables | more explains:
$? Contains the execution status of the last operation. It contains
TRUE if the last operation succeeded and FALSE if it failed.
In PS, dir is just an alias for get-ChildItem; cd likewise for Set-Location.
edit: Same question here, including an answer straight from the horse's mouth.

Powershell: Get return result from Powershell Script called from Within other PS Script

Background:
I have a powershell script:script1 that takes in a sourceDirectory and two destinations (call them dest1Directory and dest2Directory).
The sourceDirectory is structured like this:
\Source\dest1\STUFF1
and
\Source\dest2\STUFF2
script1 calls another script:script2, foreach STUFF (so script2 may be run 10 times for example), providing script2 with the necessary destination parameter, which creates backups of all of the contents "STUFF" is replacing on dest1Directory and dest2Directory, and then copies all of STUFF to the corresponding destination.
Script1:
foreach ($folder in $STUFF1)
{
& $script2 -stuffParameter $folder -destDrive $dest1Directory -backUpDrive $BackUpDirectory
}
The issue I'm having is:
I'm calling script1 from a visual studio website and would like script2 to output all of the backup directory paths it creates so I have references to them for later. I have tried this inside of script2:
$returnRollBackObj = New-Object Object
Add-Member -memberType NoteProperty -name "RollBackLocation" -value $folderobj -inputObject $returnRollBackObj
return $returnRollBackObj
But it doesn't seem to return the objects since they are subscript calls. I don't know how to return an undefined number of these objects from script1 so I'm at a loss. Can someone help me out?
Any uncaptured output is returned in Powershell.
So something like
return $returnRollBackObj
is equivalent to
$returnRollBackObj
return
So, since script2 is returning these objects, as long as these objects are not captured in script1, they will be returned by script1 as well. So you not have to do anything about "returning undefined number of objects"
Try running the scripts from console first, to see if you are getting the intended behaviour.
How's your IIS setup?
Just thinking , if you hve eimpersonation on, tha it might not have access to the second script's location due to the double hop issue.
Something else that may help is the transcript functionality. Add a start-transcript at the top of yu script/code and you'll get a dump of everything, comands and output (inc. Errors and warnings).
Edit:
Just realised your not assigning the returned values from script2 to aaything in script1.
You may need something like this:
$ret = & $script2 ...
Do something with $ret...
OR put it in an array
$ret = #()
For loop...
$temp = & $script2 ...
$ret = $ret + $temp
Then return $ret after the for loop.
Matt

Resources