2

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
  • 193,178
  • 25
  • 254
  • 328
mr netlord
  • 195
  • 1
  • 3
  • 11

4 Answers4

2

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.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
mr netlord
  • 195
  • 1
  • 3
  • 11
  • Apparently you're using PowerShell v2.0. You should've mentioned that. – Ansgar Wiechers Sep 22 '15 at 22:01
  • No, Sir - i´m using the actual PS-Version... $PSVersionTable PSVersion 5.0.10105.0 – mr netlord Sep 23 '15 at 14:29
  • Can't reproduce. `$PSScriptRoot` works just fine in PowerShell v5 (5.0.10514.6) on Windows 10 or Server 2012 R2. – Ansgar Wiechers Sep 25 '15 at 12:57
  • $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. – Tydaeus Mar 27 '19 at 21:30
1

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
)
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • I wonder if there's a better type than `[string]` for `$TarPath`? Is there no object that represents a path in .NET? – Kellen Stuart Oct 16 '19 at 20:12
  • @KolobCanyon You could use `[System.IO.FileInfo]`, but what would be the advantage? – Ansgar Wiechers Oct 16 '19 at 22:20
  • 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 – Kellen Stuart Oct 16 '19 at 22:27
  • You could use parameter validation for that. Besides, just because a path doesn't exist (yet) doesn't automatically mean it's invalid. – Ansgar Wiechers Oct 16 '19 at 22:32
1

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"
Fabio Salvalai
  • 2,479
  • 17
  • 30
0

You just need to modify the last line of the script with either of the following:

For default directory:

Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight

For custom directory:

Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath "D:\Piyush\temp"

And comment out the following line in the PROCESS block as you don't want to override the custom directory path with the default one.

$TarPath = $PSScriptRoot
Piyush
  • 830
  • 8
  • 19
  • 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) – mr netlord Sep 22 '15 at 10:02
  • 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? – Piyush Sep 22 '15 at 10:18