Code Monkey home page Code Monkey logo

powershelltraps's Introduction

PowerShellTraps is a collection of some PowerShell traps and oddities shown by demo scripts, workarounds, and automated tests. On invoking scripts change to their directory. See also TESTS. Some scripts require Invoke-PowerShell.ps1.


Index

Basic

Class

Clixml

Cmdlets

Module

PowerShell.exe


powershelltraps's People

Contributors

ghost-terms avatar kevinmarquette avatar nightroman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

powershelltraps's Issues

[suggested trap] should not use ValueFromPipeline with more than one param

I didn't notice an existing trap covering pipeline input getting bound to multiple parameters. Repro:

function foo
{
    [CmdletBinding()]
    param( [Parameter( Mandatory = $false,
                       Position = 0,
                       ValueFromPipeline = $true )]
           [string[]] $P1,

           [Parameter( Mandatory = $false,
                       Position = 1,
                       ValueFromPipeline = $true )]
           [string[]] $P2
         )

    process
    {
        try
        {
            Write-Host "`$P1: $P1" -Fore Magenta
            Write-Host "`$P2: $P2" -Fore Magenta
        }
        finally { }
    }
}

'asdf' | foo

# Expected: input gets bound to one parameter...
# Actual: ...but it gets bound to /both/

# I guess the lesson is that you should never have ValueFromPipeline on more
# than one parameter in a given parameter set, but PS lets you do that.

if statement is crazy

Another crazyness related to if statement.

Suppose we have an optional array:

$ar = (1, 2, 3)

And, suppose that we would like to create some default array if $ar is empty (yep, we'll try to emulate null-coalescing operator from C#):

$ar1 = if ($ar) {$ar} else {(1, 2)}

Ok, looks good, but what if we would like not only "coalesce" an array, but also to convert a result to, for instance, array of strings:

$ar2 = [string[]](if ($ar) {$ar} else {(1, 2)})

Unfortunately, this code fails with an error:

if : The term 'if' 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.

There is two ways to solve this problem.

Use two different statements:

$tmp = if ($ar) {$ar} else {@(1, 2, 3)}
$ar2 = [string[]]($tmp)

Or by introducing temporary variable inside if statement:

$ar3 = [string[]]($tmp = if ($ar) {$ar} else {@(1, 2, 3)})

Again, I do think this is yet another bug rather than a trap!

[suggested trap] Weird null-ness

Maybe this is already in your list, but just in case.

Consider following two cases:

$tmp = (1..5 | where {$_ -gt 6} | select -First 1)
$list1 = @(, $tmp)
$list1.Length # 0

$list2 = @(, $(if($tmp -eq $null) {$null} else {$tmp}))
$list2.Length # 1

To create an array with one element from piped expression additional magic step is required.

In first case list1 is an empty array, but in the second case it has one element equals to $null.

This is really strange for me, because for me expression like $(if($tmp -eq $null) {$null} else {$tmp}) is equivalent to $tmp.

P.S. I've found this issue trying to create closures in PowerShell. Because closures captures outer variable by value you have to wrap captured variable in array or another mutable instance. But that's another story.

Async vs. Sync event handling

Noticed interesting behavior that I can't understand.

PowerShell supports two types of event handlers: sync event handlers and async event handlers.

First one is a plain-vanilla .NET event handling and second is PowerShell-based full-featured-generic event handling.

Here small example:

Add-Type @"
public class Foo
{
  public event System.EventHandler CustomEvent;

  public void RaiseCustomEvent()
  {
      System.Console.WriteLine("Before raising CustomEvent");
      if (CustomEvent != null) {CustomEvent(this, System.EventArgs.Empty);}
      System.Console.WriteLine("After raising CustomEvent");
  }
}
"@

$foo = New-Object -TypeName Foo

# Register sync event
$foo.add_CustomEvent({
    Write-Host "Another Custom Handler"
})

# Register for async event
Register-ObjectEvent $foo -EventName CustomEvent -Action {
    Write-Host "Async CustomEventHandler"
}

# Raising the event
$foo.RaiseCustomEvent()

Running this code lead to the following output:

Before raising CustomEvent
Another Custom Handler
After raising CustomEvent
Async CustomEventHandler

So far, so good. But if to change order of subscriptions: async first and sync latter, then the output would be different:

# Register for async event
Register-ObjectEvent $foo -EventName CustomEvent -Action {
    Write-Host "Async CustomEventHandler"
}

# Register sync event
$foo.add_CustomEvent({
    Write-Host "Another Custom Handler"
})

# Raising the event
$foo.RaiseCustomEvent()

The output in this case would be:

Before raising CustomEvent
Async CustomEventHandler
Another Custom Handler
After raising CustomEvent

Basically, async event is not async any more! "Async" event handler was ran synchronously as well.

Am I missing something?

Cannot-pipe-some-expressions can be solved with `$()`

Just wanted to mention that the examples you have for Cannot-pipe-some-expressions are only incorrect because they're syntactically incorrect.

solution

$result = foreach($e in 1..5) {$e}
$result | %{"Result is $_"}

'-------------------'

$(foreach($e in 1..5) {$e}) | %{"Result is $_"}

'-------------------'

$(foreach($e in 1..5) {$e}) > z.log

notepad z.log

A provider path passed *positionally* fails to make dynamic parameters available if it is placed *after* the dynamic parameter

via PowerShell/PowerShell #19495 by mklement0:

A common idiom is to do something like Get-ChildItem -Directory $somePath.

This works fine if the provider underlying the current location happens to be the file-system provider, but fails otherwise.

Seemingly, the dynamic -Directory parameter is searched for before the positional path argument is bound (which is necessary to infer the provider context). Placing the dynamic parameter after the positional path argument fixes the problem, as does using a named argument to pass the path (-Path $somePath or -LiteralPath $somePath).

Push-Location function:  # change to a non-file-system location
# !! Fails to find -Directory - by contrast, the following DO work:
#    * Get-ChildItem $PSHOME -Directory  # positional argument comes *first*
#    * Get-ChildItem -Directory -Path $PSHOME  # *named* argument
Get-ChildItem -Directory $PSHOME
Pop-Location

Expected behavior

A listing of the subdirectories of the $PSHOME directory.

Actual behavior

Get-ChildItem: A parameter cannot be found that matches parameter name 'Directory'.

Confirmed on PowerShell 5.1 all the way through 7.4-preview.3

Unknown if this affects versions of PowerShell before 5.1

Potential collaboration with pwsh-live-doc

Hey!

A bit ago I created https://github.com/benweedon/pwsh-live-doc/ to document the dark corners of PowerShell by automatically running examples and generating a web site from them.

Today I found this repository, which is absolutely amazing! I didn't realize such a large repository of PowerShell quirks existed.

I was wondering if we could discuss some potential collaboration. I just finished the bulk of the generation logic for my site and so I'm just getting started adding more examples. I'd love to migrate things from here to my repo (and link back here of course). I'd also be curious if you can think of any ways for this repo to make use of mine. For example, my project could become a more generic PowerShell documentation generation engine, and yours could host the actual documentation and generate a site from it.

I feel like both our projects are pretty similar, and it would be great to share the load a bit. What are your thoughts?

Collection selector

This is a relatively simple trap, but I've got into it recently.

Suppose you have a collection of custom objects:

$customers = 1..10 | foreach { [pscustomobject]@{Id= $_; Name = "CustomName($)"}}

Now, let suppose you want to iterate over each element and perform some computation:

foreach($customer in $customers)
{
    if ($customers.Id -eq 2)
    {
        Write-Host "Yep, Found customer with Id == 2"
    }
}

What do you expect?

I've made an error in the script, and now instead of getting "Yep, Found customer with Id == 2" only once I'll get this message as many times as a number of items in $customers variable.

Powershell has build-in feature for getting projections from the collection. This means that if collection customers has an objects with Id property, than $customers.Id will return an array with Id properties from each element.

And due collections equality trap $customers.Id -eq 2 will always produce a sequence with one element that convertible to $True.

Unfortunately, I don't know the workaround for this trap, because this code is 100% legit. Even turning strict mode will not help.

Is "*.ext matches *.ext*" related to 8.3 names?

In Provider specific filters, it is stated that "*.ext where ext is three characters matches *.ext*".
This reminds me of a legacy Windows feature: on some systems, files with long names or extensions get a 8.3 name for DOS compatibility. For example, a file "application.docx" may get a short name "applic~1.doc" and matches *.doc. This feature can be controlled by the fsutil command.
Do you think this is the reason?

If-else blocks and array types

PowerShell drives me nuts (I hope I missing something here).

Consider following code:

$var = $True

$foo = if ($var) {@(, 1)}

$foo # 1
$foo.GetType().FullName #System.Int32

$foo = @(, 1)

$foo # 1
$foo.GetTYpe().FullName #System.Object[]

Clearly that behavior should be the same. Right? But not exactly. Here is an output:

1
System.Int32
1
System.Object[]

I would say that this is a bug, not a trap!

Powershell and lost exception origins

I've found another trap in PowerShell.

Consider following code:

Set-StrictMode -Version Latest

Function WillThrow
{
    $result = "Some string $UnknownVariable"
}

Function WillCatchAndRethrow
{
    Try
    {
        WillThrow
    }
    catch
    {
        #Write-Host "Yep, we failed! Rolling back some stuff!" 
        throw
    }
}

WillCatchAndRethrow

This is a very simplified version of production code where WillCatchAndRethrow is doing some simple recovery like rolling back transaction. This script will fail, obviously, with following error

The variable '$UnknownVariable' cannot be retrieved because it has not been set.
At line:7 char:28
+     $result = "Some string $UnknownVariable"
+                            ~~~~~~~~~~~~~~~~

So far so good. But lets change WillThrow function a little bit:

Function WillThrow
{
    [CmdletBinding()]
    param()
    $result = "Some string $UnknownVariable"
}

I've added a CmdletBinding attribute because I need some stuff that this attribute provides. Lets rerun this script once again. It will still fail, but look carefully at error message:

WillThrow : The variable '$UnknownVariable' cannot be retrieved because it has not been set.
At line:14 char:9
+         WillThrow
+         ~~~~~~~~~

Ooops! Now we've lost critical information about the error: it's origin!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.