Better custom error handling for powershell - powershell

So I have a powershell script that integrates with several other external third-party EXE utilities. Each one returns its own kind of errors as well as some return non-error related output to stderr (yes badly designed I know, I didn't write these utilities). So What I'm currently doing is parsing the output of each utility and doing some keyword matching. This approach does work but I feel that as I use these scripts and utilties I'll have to add more exceptions to what the error actually is. So I need to create something that is expandable,possibly a kind of structure I can add to an external file like a module.
I was thinking of leveraging the features of a custom PSObject to get this done but I am struggling with the details. Currently my parsing routine for each utility is:
foreach($errtype in {'error','fail','exception'})
{
if($JobOut -match $errtype){ $Status = 'Failure' }
else if($JobOut -match 'Warning'){$Status = 'Warning' }
else { $Status = 'Success' }
}
So this looks pretty straightforward until I run into some utility that contain some of the keywords in $errtype within $JobOut that is not an error. So now I have to add some exceptions to the logic:
foreach($errtype in {'error','fail','exception'})
{
if($JobOut -match 'error' -and(-not($JobOut -match 'Error Log' }
elseif($JobOut -match $errtype){ $Status = 'Failure' }
else if($JobOut -match 'Warning'){$Status = 'Warning' }
else { $Status = 'Success' }
}
So as you can see this method has the potential to get out of control quickly and I would rather not start editing core code to add a new error rule every time I come across a new error.
Is there a way to maybe create a structure of errors for each utility that contains the logic for what is an error. Something that would be easy to add new rules too?
Any help with this is really appreciated.

I would think a switch would do nicely here.
It's very basic, but can be modified very easily and is highly expandable and I like that you can have an action based on the input to the switch, which could be used for logging or remediation.
Create a function that allows you to easily provide input to the switch and then maintain that function with all your error codes, or words, etc. then simply use the function where required.
TechNet Tips on Switches
TechNet Tips on Functions

Related

How to make the output in the pipeline appear on the console when running pester tests?

By default, the output in the pipeline is hidden,But sometimes I really want to know the output at that time.
Of course, I knew I could add additional commands, such as write-host or out-default.
But does Pester itself have a mechanism to make the output display properly?
I checked the help document and didn't find the relevant content, so I came here for help.
It is possible to write a custom Should wrapper (proxy) using this technique. The wrapper can write the pipeline objects to the console.
Here is a complete example of such a Should wrapper and how to override Pester's Should.
To apply it to your case, edit the process{} block of the wrapper like this:
process {
try {
# Here $_ is the current pipeline object
Write-Host "Current test case input:`n$( $_ | Out-String )"
# forward it to "process" block of original "Should"
$steppablePipeline.Process( $_ )
} catch {
throw
}
}

Invoke-Pester to only run a single Assert/It block

I am writing unit tests for my Powershell Modules, with a file for each module, and Describe blocks for each function. Context blocks organize the tests along the behavior I am trying to test for with some arrange code, and my It blocks contain minimal arrange/act code and a assert.
I can limit my tests to only run a single test file by using Invoke-Pester "Path/To/Module"
I can also limit based on the Describe blocks by using Invoke-Pester "Path/To/Module" -TestName #("RunThisDescribe","AndRunThisDescribe")
As I am adding a new assertion (via a new It block) to an existing file/Describe/Context, I want to test/debug my new assertion alone, without the rest of the assertions of a given describe/context running (but with any mocks/variables that I set at the describe/context scope still available.
I have been commenting out my existing assertions while I am developing the new one, then remove the block comments and run them all after I am finished with the new test. This works, but is clunky.
Is there a way to run Invoke-Pester to only execute a given list of Its?
Is there a better workflow for developing/debuging new tests other than either letting all of them run or commenting them out?
I know, this question is pretty old, but it deserves an update:
Starting with Pester version 5, you can have a -Tag on everything: Describe, Context, It
This makes it really easy to run specific assertions and nothing else.
You can even exclude specific code with -ExcludeTag.
Please see https://github.com/pester/Pester#tags for details.
Also check out the braking changes, if you plan to migrate from version 4 to 5!
It doesn't look like there's any way to specify tests to run by the name of the It block.
You could split your new test in to a new Describe block and then run it via the -TestName parameter as you described, or give it a -Tag and then specify that tag via Invoke-Pester, however that doesn't seem to work for a nested Describe, it has to be at the top level.
I assume this wouldn't work for you because your Mocks and Variables would be in the other describe.
VSCode with the PowerShell extension installed allows you to run individual Describe blocks via a "Run Tests" link at the top of the Describe and this does work for nested blocks. However i'm not sure if this would result in the Mocks/Variables from the parent Describe block being invoked (my guess would be not).
Example of nested Describe, which can be individually run within VSCode:
Describe 'My-Tests' {
It 'Does something' {
$true | Should -Be $true
}
Describe 'NewTest' {
It 'Does something new' {
$true | Should -Be $true
}
}
}
It's a shame you can't currently put Tags on Context blocks for filtering in/out certain sets of tests. That was requested as a feature 2 years ago but it seems is not simple to implement.
To add to Tofuburger's answer, and based on Pester 5.3.1, you can also manipulate tests programmatically, in your test scripts, based on tags.
Describe 'Colour' -Tag 'Epistemology' {
BeforeAll {
$ParentBlockTags = $____Pester.CurrentBlock.Tag
if ($ParentBlockTags -eq 'Epistemology')
{
Set-ItResult -Inconclusive
}
}
BeforeEach {
$ItTags = $____Pester.CurrentTest.Tag
if ($ItTags -eq 'HSL')
{
Set-ItResult -Skipped -Because 'Not implemented'
}
}
It 'Saturates' -Tag 'HSL' {
1 | Should -Be 2
}
It 'Greens' -Tag 'RGB' {
1 | Should -Be 3
}
}

Using querySelectorAll on an mshtml.HTMLDocumentClass object in PowerShell causes a crash

I'm trying to do some web-scraping via PowerShell, as I've recently discovered it is possible to do so without too much trouble.
A good starting point is to just fetch the HTML, use Get-Member, and see what I can do from there, like so:
$html = Invoke-WebRequest "https://www.google.com"
$html.ParsedHtml | Get-Member
The methods available to me for fetching specific elements appear to be the following:
getElementById()
getElementsByName()
getElementsByTagName()
For example I can get the first IMG tag in the document like so:
$html.ParsedHtml.getElementsByTagName("img")[0]
However after doing some more research in to whether I could use CSS Selectors or XPath I discovered that there are unlisted methods available, since we are just using the HTML Document object documented here:
querySelector()
querySelectorAll()
So instead of doing:
$html.ParsedHtml.getElementsByTagName("img")[0]
I can do:
$html.ParsedHtml.querySelector("img")
So I was expecting to be able to do:
$html.ParsedHtml.querySelectorAll("img")
...in order to get all of the IMG elements. All the documentation I've found and googling I've done supports this. However, in all my testing this function crashes the calling process and reports a heap corruption exception code in the Event Log (0xc0000374).
I'm using PowerShell 5 on Windows 10 x64. I've tried it in a Win10 x64 VM that is a clean build and just patched up. I've also tried it in Win7 x64 upgraded to PowerShell 5. I haven't tried it on anything prior to PowerShell 5 as all our systems here are upgraded, but I probably will once I have time to spool a new vanilla VM for testing.
Has anyone run in to this issue before? All my research so far is a dead end. Are there alternatives to querySelectorAll? I need to scrape pages that will have predictable sets of tags inside unpredictable layouts and potentially no IDs or classes assigned to the tags, so I want to be able to use selectors that allow structure/nesting/wildcards.
P.S. I've also tried using the InternetExplorer.Application COM object in PowerShell, the result is the same, except instead of PowerShell crashing Internet Explorer crashes. This was actually my original approach, here's the code:
# create browser object
$ie = New-Object -ComObject InternetExplorer.Application
# make browser visible for debugging, otherwise this isn't necessary for function
$ie.Visible = $true
# browse to page
$ie.Navigate("https://www.google.com")
# wait till browser is not busy
Do { Start-Sleep -m 100 } Until (!$ie.Busy)
# this works
$ie.document.getElementsByTagName("img")[0]
# this works as well
$ie.document.querySelector("img")
# blow it up
$ie.document.querySelectorAll("img")
# we wanna quit the process, but since we blew it up we don't really make it here
$ie.Quit()
Hope I'm not breaking any rules and this post makes sense and is relevant, thanks.
UPDATE
I tested earlier PowerShell versions. v2-v4 crash using the InternetExplorer.Application COM method. v3-4 crash using the Invoke-WebRequest method, v2 doesn't support it.
I ran into this problem, too, and posted about it on reddit. I believe the problem happens when Powershell tries to enumerate the HTML DOM NodeList object returned by querySelectorAll(). The same object is returned by childNodes() which can be enumerated by PS, so I'm guessing there's some glue code written for .ParsedHtml.childNodes but not .ParsedHtml.querySelectorAll(). The crash can be triggered by Intellisense trying to get tab-complete help for the object, too.
I found a way around it, though! Just access the native DOM methods .item() and .length directly and emit the node objects into a PowerShell array. The following code pulls the newest page of posts from /r/Powershell, gets the post list anchors via querySelectorAll() then manually enumerates them using the native DOM methods into a Powershell-native array.
$Result = Invoke-WebRequest -Uri "https://www.reddit.com/r/PowerShell/new/"
$NodeList = $Result.ParsedHtml.querySelectorAll("#siteTable div div p.title a")
$PsNodeList = #()
for ($i = 0; $i -lt $NodeList.Length; $i++) {
$PsNodeList += $NodeList.item($i)
}
$PsNodeList | ForEach-Object {
$_.InnerHtml
}
Edit .Length seems to work capitalized or lower-case. I would have expected the DOM to be case-sensitive, so either there's some things going on to help translate or I'm misunderstanding something. Also, the CSS selector is grabbing the source links (self.PowerShell mostly), but that it my CSS selector logic error, not a problem with querySelectorAll(). Note that the results of querySelectorAll() are not live, so modifying them won't modify the original DOM. And I haven't tried modifying them or using their methods yet, but clearly we can grab at the very least .InnerHtml.
Edit 2: Here is a more-generalized wrapper function:
function Get-FixedQuerySelectorAll {
param (
$HtmlWro,
$CssSelector
)
# After assignment, $NodeList will crash powershell if enumerated in any way including Intellisense-completion while coding!
$NodeList = $HtmlWro.ParsedHtml.querySelectorAll($CssSelector)
for ($i = 0; $i -lt $NodeList.length; $i++) {
Write-Output $NodeList.item($i)
}
}
$HtmlWro is an HTML Web Response Object, the output of Invoke-WebReqest. I originally tried to pass .ParsedHtml but then it would crash on assignment. Doing it this way returns the nodes in a Powershell array.
The #midnightfreddie's solution worked fine for me before, but now it throws Exception from HRESULT: 0x80020101 when calling $NodeList.item($i).
I found the following workaround:
function Invoke-QuerySelectorAll($node, [string] $selector)
{
$nodeList = $node.querySelectorAll($selector)
$nodeListType = $nodeList.GetType()
$result = #()
for ($i = 0; $i -lt $nodeList.length; $i++)
{
$result += $nodeListType.InvokeMember("item", [System.Reflection.BindingFlags]::InvokeMethod, $null, $nodeList, $i)
}
return $result
}
This one works for New-Object -ComObject InternetExplorer.Application as well.

creating GUI forms without variables

I am trying to find a way to create a form in PowerShell without using any variables unless they are temporarily or virtually assigned. I want to be able to run a command similar to this:
(New-Object System.Windows.Forms.Form).ShowDialog()
where I can enter in a code into an event that is triggered once the form is created. That event will then be responsible for creating all the objects and other events inside the form. Once the form is launched, I will not need any variables accept for the ones that are virtually assigned within the events.
This to avoid using too much system resources from assigning and endless amount of variables for each object in the form. The script that I am currently working on in PowerShell is very possibly going to be really big, and even if it is not a very large script, efficiency and clean code is always the key to writing a good program or script.
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x.ShowDialog()
Remove-Variable x
It is very possible to access these objects still with the exact same kind of access when you define each object with a variable. It cost me many hours of research and just simply attempting random commands to find out how to do this. Here is all the commands you may need to relearn if you are interested in my solution:
# create item in form:
$x.Controls.Add((New-Object System.Windows.Forms.Button))
# access the last created item in the form:
$x.Controls[-1]
# change it's name to identify it easier
$x.Controls[-1].Name = 'button1'
# access the item by it's new name:
$x.Controls['button']
# delete the item by it's name:
$x.Controls.Remove($x.Controls['button1'])
If your familiar with form creation in PowerShell then this should all make sense to you and you should be familiar with how the rest of it works. Also, another note to make for those who are interested in what I am trying to do is that any of these commands can be done within an event by replacing $x with $this. If it is inside an event of an object inside the "controls" section of the form, then you would use $this.parent.
This is exactly what I mean by having the ability to create a form with virtually no variables. The only problem I am having with this is that I am unsure how to assign an event and call the method ShowDialog() at the same time.
I found an a very interesting solution to this, however I am not sure to what the limits are to this solution and it dose not quite work in the way that I would personally like it to.
file.ps1:
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x
remove-variable x
command to execute the code:
(iex(Get-Content 'file.ps1'|out-string)).ShowDialog()

Can I dynamically detect the Zend framework in the include path?

I'm developing a plugin for WordPress that will add additional functionality if the Zend framework is available, but the functionality added is not great enough to justify the user installing the framework if it does not already exist.
My question is, is there any good way to detect if Zend exists? Obviously I can use get_include_path() to return whatever the include path is, but beyond that I'm not sure. I could use regexes to determine if the phrase zend appears in the paths, but that seems unreliable at best (more thinking false positives than false negatives, but I think both have a potential if people haven't used the default path).
If I have to resort to this regex, I can always trap the errors as they come and proceed from there, but if there's a better way then that would be useful to know.
Simplest way:
if (stream_resolve_include_path('Zend/Version.php')) {
// ZF found
}
but I would question why you need to do this. If your plugin needs to be coded to work without the framework, what do you gain by using it if it's there? Seems like this would just complicate your code.
Yes this is somewhat easy:
$zfPresent = (bool) stream_resolve_include_path("Zend/Application.php")
If the file could be found inside the include paths stream_resolve_include_path() will return it's absoulte path - if not it will return false.
So if it is found the framework is definatly there.
Only grain of salt for some people: It requires at least PHP 5.3.2
See: http://php.net/manual/de/function.stream-resolve-include-path.php
If the PHP version does not allow you to use the above solution:
Try something like this:
set_error_handler(function($code, $text, $file, $line) {
// Handle zend not present
/* So that internal error handling won't be executed */
return true;
});
include("Zend/Application.php");
restore_error_handler();
I don't think it's really elegant but it should be somewhat reliable in detecting Zend. Another way may be something like:
function checkForZf()
{
$includePaths = array_merge(explode(PATH_SEPARATOR, get_include_path(), array($myAppsLib));
foreach($includePaths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . 'Zend' . DIRECTORY_SEPARATOR . 'Application.php') {
return true;
}
}
return false;
}
This should be also somewhat reliable but file actions are performance expensive. You may test it out in regards to performance or store the state somewhere after first detection so it does not need to run on every request.