Capture line number of exception in dot sourced file - powershell

So I am running a powershell script within my script, whenever it fails the line number of the exception is the line number of where I call the dot sourced file in my parent script.
I tried to catch the exception, but that didn't contain the full exception, only the actual exception text (no line numbers or categories).
Has anyone solved this? I've been searching high and low and haven't found anything about this.

Error objects have a number of properties.
Output of $Error[0] | Get-Member | Select Name | Set-Clipboard:
Name
Equals
GetHashCode
GetObjectData
GetType
ToString
CategoryInfo
ErrorDetails
Exception
FullyQualifiedErrorId
InvocationInfo
PipelineIterationInfo
ScriptStackTrace
TargetObject
PSMessageDetails
So, you could throw the details to the console via a function, for example:
Function Write-ErrorDetails($ErrorObject)
{
$thisError = [PSCustomObject]#{
Exception = $ErrorObject.Exception
Message = $ErrorObject.Exception.Message
FQID = $ErrorObject.FullyQualifiedErrorId
InovcationInfo = $ErrorObject.InvocationInfo
ScriptStackTrace = $ErrorObject.ScriptStackTrace
TargetObject = $ErrorObject.TargetObject
}
return $thisError
}
In your script, if you have a try/catch block, you could catch exceptions and call your function:
BEGIN
{
Function Write-ErrorDetails($ErrorObject)
{
$thisError = [PSCustomObject]#{
Exception = $ErrorObject.Exception
Message = $ErrorObject.Exception.Message
FQID = $ErrorObject.FullyQualifiedErrorId
InovcationInfo = $ErrorObject.InvocationInfo
ScriptStackTrace = $ErrorObject.ScriptStackTrace
TargetObject = $ErrorObject.TargetObject
}
return $thisError
}
}
PROCESS
{
try
{
Do-SomeNonExistentCommand
}
catch
{
Write-ErrorDetails -ErrorObject $Error[0]
}
}
END{}
If saved inside of a file called 123.ps1 and run, your output would look like this:
Exception : System.Management.Automation.CommandNotFoundException: The term
'Do-SomeNonExistentCommand' 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 System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(
FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(Int
erpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction
.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction
.Run(InterpretedFrame frame)
Message : The term 'Do-SomeNonExistentCommand' 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.
FQID : CommandNotFoundException
InovcationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at <ScriptBlock><Process>, C:\Users\Pythagoras\desktop\123.ps1: line 22
at <ScriptBlock>, <No file>: line 1
TargetObject : Do-SomeNonExistentCommand
The ScriptStackTrace property might be helpful for troubleshooting, especially if you are writing scripts/tools for an audience and not just your own use. You could add additional functions to do logging with the objects Write-ErrorDetails can provide, etc etc

Related

""Missing argument in parameter list."" when trying to define a list of Site IDs

I have the following as part of a Power-Shell script:=
Connect-ExchangeOnline
$SiteIDs = (64898c8f-2d5f-4e0e-9a9b-eb9828975a9e,20e6140c-0441-4988-b36c-c61cf3400847)
where i am trying to define a list of site IDs, but the above is returning this error:-
PS C:\WINDOWS\system32> $SiteIDs = (64898c8f-2d5f-4e0e-9a9b-eb9828975a9e,20e6140c-0441-4988-b36c-c61cf3400847)
At line:1 char:49
+ $SiteIDs = (64898c8f-2d5f-4e0e-9a9b-eb9828975a9e,20e6140c-0441-4988-b ...
+ ~
Missing argument in parameter list.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingArgument
Any advice?
Thanks
Your SiteIDs should probably be a list of strings. Therefore, you must put them into quotes (and you can omit the parentheses):
$SiteIDs = '64898c8f-2d5f-4e0e-9a9b-eb9828975a9e','20e6140c-0441-4988-b36c-c61cf3400847'
Well, in my case I have no idea what's wrong. The script has been working fine for years under PS ver 5.
7.3.1 though, throws this error in a line as simple as this:
$OU365path = 'ou=Users,ou=O365,dc=contoso,dc=com'
and specifically underlines the first comma.
What's wrong here?
The same line, executed directly in the console does not throw an error.

When attempting to use Import-Clixml on a List<T>, the return value is System.Object

Here is a simplified and complete code example:
class DediInfo {
[string]$ServiceName
[int]$QueryPort
DediInfo([string]$servicename, [int]$qPort){
$this.ServiceName = $servicename
$this.QueryPort = $qPort
}
}
class DediInfos {
hidden static [DediInfos] $_instance = [DediInfos]::new()
static [DediInfos] $Instance = [DediInfos]::GetInstance()
[System.Collections.Generic.List[DediInfo]]$Dedis = [System.Collections.Generic.List[DediInfo]]::New()
hidden static [DediInfos] GetInstance() {
return [DediInfos]::_instance
}
[void]SaveInfo(){
$this.Dedis | Export-Clixml "D:\test.xml"
}
[void]LoadInfo() {
$this.Dedis = Import-Clixml "D:\test.xml"
}
}
$dInfos = [DediInfos]::Instance
$dInfos.Dedis.Add([DediInfo]::New("service1", 15800))
$dInfos.Dedis.Add([DediInfo]::New("service2", 15801))
$dInfos.SaveInfo()
$dInfos.Dedis = [System.Collections.Generic.List[DediInfo]]::New()
$dInfos.LoadInfo()
And here is the Exception I am receiving:
Exception setting "Dedis": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Collections.Generic.List`1[DediInfo]"."
At D:\test.ps1:25 char:9
+ $this.Dedis = Import-Clixml "D:\test.xml"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
I've spent the past 4 hours trying different permutations:
adding [Serializable()] to the first class
using the ConvertTo-Xml on the List<> before saving and after reading from file
breaking out the load steps to dump into a new list first - which does load the data from file, but I can't put it back to the singleton's List<> because it loads as System.Object
Tried using saving to json (below) and same error message
$this.Dedis | ConvertTo-Json | Set-Content "D:\test.json"
$this.Dedis = Get-Content -Raw "D:\test.json" | ConvertFrom-Json
Lots of other attempts that I cannot remember any longer.
What I'm wanting is just a way to save the List<> to a file and then load it back in again on my next script's run. The example is very simplified; it saves, clears, and then tries to load again to show what I'm dealing with. Everywhere I've read is that the Import-Clixml should load the XML file back in as the original object, but I'm not having any luck.
Import-XML indeed load back the object, but does not quite respect the type.
Prior export, you had a list of DediInfo objects.
After import, you now have an array of Deserialized.DediInfo objects.
You can see that by doing an import and checking the base type of the first dediinfo object.
$Imported = Import-Clixml "D:\test.xml"
$Imported[0].psobject.TypeNames
It will show
Deserialized.DediInfo
Deserialized.System.Object
Hence, your conversion is failing because you are trying to cast it back to its original type, which is not possible.
You will have to build your list of DediInfo again after importing the XML.
Here's a simple modification to your LoadInfo that will work.
[void]LoadInfo() {
$this.Dedis = Foreach ($i in Import-Clixml "D:\test.xml") {
[DediInfo]::New($i.ServiceName, $i.QueryPort)
}
}

Intermittent PowerShell "You cannot call a method on a null-valued expression" exception

Very infrequently (less than 0.1% of function calls) I see an exception from inside a function. The function writes a log entry to a common log file and the intent is that it will wait until it can obtain the mutex that controls writing to the common file.
Relevant code snippet:
function WriteSummary ([string]$fnContent)
{
$fnTimeStamp = Get-Date -Format "HH:mm:ss.ff"
Try {
[void]$SummaryLogMutex.WaitOne()
[System.IO.File]::AppendAllText($script:SummaryLog,("$fnTimeStamp $fnContent") + ([Environment]::NewLine))
} Catch {
$FullException = ($_ | Format-List -Force | Out-String)
WriteActivity -fnContent ("$FullException")
} Finally {
[void]$SummaryLogMutex.ReleaseMutex()
}
}
$SummaryLogMutex = New-Object System.Threading.Mutex($false, "Global\SummaryLogMutex")
WriteSummary -fnContent ("Some text")
The exception is as follows:
Exception : System.Management.Automation.RuntimeException: You
cannot call a method on a null-valued expression.
at System.Management.Automation.ExceptionHandlingOps
.CheckActionPreference(FunctionContext funcContext,
Exception exception)
at System.Management.Automation.Interpreter.ActionCa
llInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTry
CatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTry
CatchFinallyInstruction.Run(InterpretedFrame frame)
The trace indicates it is "[void]$SummaryLogMutex.WaitOne()" causing the exception.
I would be grateful for help with understanding and resolving what is going on.
Modified code to be tested to create object inside try block and avoid invoking method on variable set to null:
function WriteSummary ([string]$fnContent)
{
$fnTimeStamp = Get-Date -Format "HH:mm:ss.ff"
Try {
If (!$script:SummaryLogMutex) {
$script:SummaryLogMutex = New-Object System.Threading.Mutex($false, "Global\SummaryLogMutex")
WriteActivity -fnContent ("Created Summary Log Mutex")
}
[void]$script:SummaryLogMutex.WaitOne()
[System.IO.File]::AppendAllText($script:SummaryLog,("$fnTimeStamp $fnContent") + ([Environment]::NewLine))
} Catch {
$FullException = ($_ | Format-List -Force | Out-String)
WriteActivity -fnContent ("$FullException")
} Finally {
[void]$script:SummaryLogMutex.ReleaseMutex()
}
}
WriteSummary -fnContent ("Some text")
I'm not able to reproduce this behavior ($SummaryLogMutex intermittently resolves to $null inside WriteSummary even though it exists in the callers scope), but I suspect you can avoid it entirely by using your named mutex differently.
Since it has a name, we can easily obtain a handle to it without relying on $SummaryLogMutex:
function WriteSummary ([string]$fnContent)
{
$fnTimeStamp = Get-Date -Format "HH:mm:ss.ff"
Try {
[void][System.Threading.Mutex]::OpenExisting("Global\SummaryLogMutex").WaitOne()
[System.IO.File]::AppendAllText($script:SummaryLog,("$fnTimeStamp $fnContent") + ([Environment]::NewLine))
}
Now we can reliably catch failures:
catch [System.Threading.WaitHandleCannotBeOpenedException] {
# handle non-existing mutex (eg. create it)
}

Why does ParseExact in my PowerShell script accept an int64 and not a string?

Although I was able to achieve my objective, I don't understand why and I am concerned I'm still doing something wrong.
The context is to retrieve a datetime value for comparison to another date using FTP to retrieve a file's timestamp. The datetime is received as a string using this:
$FTPrequest = [System.Net.FtpWebRequest]::Create($FTPTargetFile)
$FTPrequest.Method = [System.Net.WebRequestMethods+FTP]::GetDateTimestamp
$FTPrequest.Credentials = $Credentials
$response = $FTPrequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
if ($tokens[0] -eq 213) {
$Timestampdata = $tokens[1]
} else {
Write-Output "FTP timestamp request for " + $FTPTargetFile + " returned an error"
}
Almost every available resource I found made it clear that ParseExact uses a string to derive a datetime as in the following line of script:
$d = [DateTime]::ParseExact($Timestampdata,"yyyyMMddHHmmss",$null)
But whether in a script or at the command prompt, the line above consistently returns the following error:
Exception calling "ParseExact" with "3" argument(s): "String was not recognized
as a valid DateTime."
At C:\hw-sw\powershell\FTPupload option - getFileInfo.ps1:38 char:1
+ $d = [DateTime]::ParseExact($Timestampdata,"yyyyMMddHHmmss",$null)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FormatException
There were 2 links I found (lin1, link2 - 2nd link addresses a slighty different problem but the point is still made) that suggested ParseExact() uses an intXX instead, as in the following line of script:
$d = [DateTime]::ParseExact([convert]::ToInt64($Timestampdata),"yyyyMMddHHmmss",$null)
And yes, the line above runs without error.
While I am grateful for having found a potential solution to my scripting problem, I am unsettled because I don't understand why ParseExact is working with an int64, and not a string as I would have expected.
Is there some setup in PowerShell that changes how it works? I believe I must be making some simple mistake, but I can't figure it out.
static datetime ParseExact(string s, string format, System.IFormatProvider provider)
static datetime ParseExact(string s, string format, System.IFormatProvider provider, System.Globalization.DateTimeStyles style)
static datetime ParseExact(string s, string[] formats, System.IFormatProvider provider, System.Globalization.DateTimeStyles style)
These are the overloaded methods for ParseExact on PSv5.1. You're passing it a $null provider.
Additional information
Your use case
After more reading, ensure your first argument doesn't have spaces or it will fail. Also make sure your current culture is correct for the behavior you're expecting.

'Set-AzureWebsite' is not recognized as the name of a cmdlet,

I am trying to run following powershell cmdlet on my azurewebsite
public string RunPowerShellScript()
{
try
{
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
//Here's how you add a new script with arguments
Command myCommand = new Command("Set-AzureWebsite");
myCommand.Parameters.Add("Name", "rmssimple");
myCommand.Parameters.Add("HostNames ", "#('test.posxyz.com', 'xyz.com')");
pipeline.Commands.Add(myCommand);
var results = pipeline.Invoke();
return Content(results[0].ToString());
}
catch (Exception ex)
{
return Content(ex.Message);
}
}
but i get bellow error
The term 'Set-AzureWebsite' 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.
firts question : why do you wan tto do it like that? second, if you want to do it , you need to import the module first and the Powershell tools need to be installed. Importing the module is as follwos in PoSh : import-module Azure