How to use path as parameter?

14,018

Solution 1

OK, I solved it myself.

First of all [string]$TarPath = "$PSScriptRoot" doesn't work at all! The variable is always empty.

However, my first idea was to define $TarPath and leave it unchanged until it defined again. This turned out that doesn't work.

Here is my solution:

# Define The Target Path
Write-Host "Please enter Screenshot-Path"
$TarPath = Read-Host "Else the screenshot will be in $PWD"
if (!$TarPath) {$TarPath = $pwd}

If nothing is entered at the prompt $pwd will be used.

Solution 2

Well, yes, that's about it, that approach will work. The only thing is that in the PROCESS block, you re-assign once again your $TarPath, making your fallback mechanism ineffective:

$TarPath = $PSScriptRoot

Delete that line and it will work like a charm.

Additionally, you could add validations such as making sure the parameter can be null, but not empty, and must be a valid path:

[ValidateScript({if ($_){  Test-Path $_}})]
[string]$TarPath = "$PSScriptRoot"

One last thing, if you want, as described in your question, to let the user use -Path on the call, you can also add an alias to your Param.

[Alias('Path')]
[ValidateScript({if ($_){  Test-Path $_}})]
[string]$Path = "$PSScriptRoot"

Solution 3

You redifine $TarPath in your function body:

$TarPath = $PSScriptRoot

This unconditionally supersedes any value previously assigned to the parameter. Remove the line and you can pass the parameter like this:

Take-Screenshot -TarPath 'C:\some\folder'

or omit the parameter to leave it at its default value ($PSScriptRoot).

I'd recommend to also change the line

$target = "$TarPath\screenshot-$stamp.png"

into this:

$target = Join-Path $TarPath "screenshot-$stamp.png"

so you don't need to fiddle around with trailing backslashes.

Function Take-Screenshot {
    [CmdletBinding()]
    Param(
        [string]$Width,
        [string]$Height,
        [string]$TarPath = "$PSScriptRoot"
    )

    PROCESS {
        [Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null

        # Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
        $bounds = [Windows.Forms.SystemInformation]::VirtualScreen

        # Define The Target Path
        $stamp = get-date -f MM-dd-yyyy_HH_mm_ss
        $target = Join-Path $TarPath "screenshot-$stamp.png"

        # Take the Screenshot
        $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
        $graphics = [Drawing.Graphics]::FromImage($bmp)
        $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
        $bmp.Save($target)
        $graphics.Dispose()
        $bmp.Dispose()
    }
}

Addendum: There are two scenarios where defining the default value for the parameter -TarPath as $TarPath = "$PSScriptRoot" doesn't work:

  • The parameter is defined as a parameter to the script (not to a function within the script) and the script is run from CMD:

    powershell -File 'C:\path\to\script.ps1'
    
  • The script is run with PowerShell v2. The variable was only available in modules prior to PowerShell v3.

In both scenarios "$PScriptRoot" can be replaced with $PWD.Path:

[CmdletBinding()]
Param(
    [string]$Width,
    [string]$Height,
    [string]$TarPath = $PWD.Path
)
Share:
14,018
mr netlord
Author by

mr netlord

Updated on June 04, 2022

Comments

  • mr netlord
    mr netlord almost 2 years

    I would like to have a screenshot tool in PS. Because I don't want to reinvent the wheel I searched and found a script at github (https://github.com/mikepruett3/psfetch), which I adapted for my needs.

    Now I would like to change the behaviour - when the script is started with no parameter it should make a screenshot in the current directory. If the user enters a path (with -Path) the screenshot should be saved there.

    My idea was to define (in my case) $Tarpath and redefine it when the option is given. How to do this?

    Here is my actual script:

    # PSFetch.ps1
    # A Screenfetch writen in PowerShell
    #
    # -----------------------------------------------------------
    # The Original Inspirations for CMDfetch:
    # -----------------------------------------------------------
    # screenFetch by KittyKatt
    #   https://github.com/KittyKatt/screenFetch
    #   A very nice screenshotting and information tool. For GNU/Linux (Almost all Major Distros Supported) *This has been ported to Windows, link below.*
    #
    # archey by djmelik
    #   https://github.com/djmelik/archey
    #   Another nice screenshotting and information tool. More hardware oriented than screenFetch. For GNU/Linux
    # -----------------------------------------------------------
    #
    
    # DONE: Function to Take the Screenshot
    Function Take-Screenshot {
        [CmdletBinding()]
        Param(
            [string]$Width,
            [string]$Height,
            [string]$TarPath = "$PSScriptRoot"
        )
    
        PROCESS {
            [Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null
    
            # Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
            $bounds = [Windows.Forms.SystemInformation]::VirtualScreen
            # Check Path for Trailing BackSlashes
    #           $TarPath = $PSScriptRoot
            if ( $TarPath.EndsWith("\") ) {
                $TarPath = $TarPath.Substring(0,$Path.Length-1)
            }
    
            # Define The Target Path
            $stamp = get-date -f MM-dd-yyyy_HH_mm_ss
            $target = "$TarPath\screenshot-$stamp.png"
    
            # Take the Screenshot
            $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
            $graphics = [Drawing.Graphics]::FromImage($bmp)
            $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
            $bmp.Save($target)
            $graphics.Dispose()
            $bmp.Dispose()
        }
    }
    
    # DONE: Fix support for Multiple Monitors
    # FROM: Shay Levy's Response -     http://stackoverflow.com/questions/7967699/get-screen-resolution-using-wmi-powershell-in-windows-7
    $ScreenWidth = 0
    $ScreenHeight = 0
    Add-Type -AssemblyName System.Windows.Forms
    $DisplayCount = [System.Windows.Forms.Screen]::AllScreens.Bounds.Count
    $Bounds = [System.Windows.Forms.Screen]::AllScreens | Select-Object -ExpandProperty Bounds
    
    $ScreenWidth = $Bounds | Measure-Object -Property Width -Sum | Select-Object -ExpandProperty Sum
    $ScreenHeight = $Bounds | Measure-Object -Property Height -Maximum | Select-Object -ExpandProperty Maximum
    
    $RESOLUTION = "$ScreenWidth x $ScreenHeight"
    
    # Take Screenshot if the Parameters are assigned...
    Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath $target
    

    edit i forgot to remove the $tarpath int the PROCESS-block. It remained here from my first tests...

    • Ansgar Wiechers
      Ansgar Wiechers over 8 years
      Please do not put tags in the subject. To mark a question as solved accept the answer that resolved the problem for you.
    • Kellen Stuart
      Kellen Stuart over 7 years
      But reinventing the wheel is fun :p
  • mr netlord
    mr netlord over 8 years
    If i change it this was the script creates two screenshots... I tried ./psfetch -tarpath "D:\aaa" and it makes one in the actual dir and the second in D:\ (and not in the subdir)
  • Piyush
    Piyush over 8 years
    First, you're supposed to call any one of the Take-Screenshot with any one of the given approach. Either with 3 args or 2 args as per your need. In that way there will only be 1 screenshot generated. Next, for D:\aaa can you confirm if the folder, aaa, already exist or not?
  • Ansgar Wiechers
    Ansgar Wiechers over 8 years
    Apparently you're using PowerShell v2.0. You should've mentioned that.
  • mr netlord
    mr netlord over 8 years
    No, Sir - i´m using the actual PS-Version... $PSVersionTable PSVersion 5.0.10105.0
  • Ansgar Wiechers
    Ansgar Wiechers over 8 years
    Can't reproduce. $PSScriptRoot works just fine in PowerShell v5 (5.0.10514.6) on Windows 10 or Server 2012 R2.
  • Tydaeus
    Tydaeus about 5 years
    $PSScriptRoot doesn't exist during parameter initialization; it gets defined afterwards. I've worked around this by doing something like if (-not $PSBoundParameters.ContainsKey('Path') { $Path = $PSScriptRoot } after the starting param block, similar to Ansgar's answer.
  • Kellen Stuart
    Kellen Stuart over 4 years
    I wonder if there's a better type than [string] for $TarPath? Is there no object that represents a path in .NET?
  • Ansgar Wiechers
    Ansgar Wiechers over 4 years
    @KolobCanyon You could use [System.IO.FileInfo], but what would be the advantage?
  • Kellen Stuart
    Kellen Stuart over 4 years
    Idk, maybe there's some built in validation or something useful in that class? I find myself having to write if(-not (Test-Path $path)) { Write-Error "invalid path: $path" } a lot
  • Ansgar Wiechers
    Ansgar Wiechers over 4 years
    You could use parameter validation for that. Besides, just because a path doesn't exist (yet) doesn't automatically mean it's invalid.