0

The following shortcut is created in SendTo and calls a PowerShell script. I want this script to be invisible (-WindowStyle Hidden) as the script uses Add-Type -AssemblyName System.Windows.Forms; $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = $parent } and processes results based on the item selected in the OpenFileDialog.

$oShell = New-Object -ComObject Shell.Application
$lnk = $WScriptShell.CreateShortcut("$Env:AppData\Microsoft\Windows\SendTo\Get Info.lnk")
$lnk.TargetPath = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
$lnk.Arguments = "-WindowStyle Hidden -NoProfile -File `"C:\Scripts\Get Info.ps1`""
$lnk.Save()

However, the script is not silent, and throws up a blue PowerShell window briefly before the OpenFileDialog. How can I make the script completely silent when called by the shortcut?

YorSubs
  • 3,194
  • 7
  • 37
  • 60
  • 1
    Does this answer your question? [How to hide the PowerShell window when running a .ps1 script in Task Scheduler?](https://stackoverflow.com/questions/46808635/how-to-hide-the-powershell-window-when-running-a-ps1-script-in-task-scheduler) – zett42 Oct 24 '22 at 05:52
  • That's helpful thanks, but @Santiago Squarzon's answer is complete. – YorSubs Oct 24 '22 at 06:07

2 Answers2

4

Only way I know about to ensure this is using to launch your .ps1 script. It goes as follows:

  • Launcher.vbs

0 as argument in objShell.Run command,0 stands for Hide the window (and activate another window.)

Set objShell = CreateObject("WScript.Shell")
path = WScript.Arguments(0)

command = "powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy ByPass -File """ & path & """"

objShell.Run command,0
  • myScript.ps1
Add-Type -AssemblyName System.Windows.Forms

$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
    InitialDirectory = $parent
}

$FileBrowser.ShowDialog()

Now in your shortcut's Target field you can use use:

wscript path\to\launcher.vbs path\to\myScript.ps1

You can read more about this method in this site.

Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    I have an argument incoming from `SendTo`. Had to mess around with the syntax a lot, but this works. `arg = WScript.Arguments(1)` to pick up the first incoming argument, then `PSCommand = "powershell -NoProfile -ExecutionPolicy ByPass -File """ & path & """ """ & arg & """"`. Fortunately, I only anticipate a single input argument for this, not sure of the syntax if more arguments (that could get tricky ... split up the arguments into an array then construct a string with `""" & x & """` around each and then pass that string to the `powershell.exe`. Could get gnarly. – YorSubs Oct 24 '22 at 12:11
  • @YorSubs that's good to know, I honestly don't know how to deal with more than one argument, would need to investigate. It's nice you could find out for yourself and I think you could complement my answer by adding a self answer with your findings. It would help future readers – Santiago Squarzon Oct 24 '22 at 12:23
1

To expand upon Santiago's great answer, and with a real world example, my goal was to take an input from SendTo and then pass that input to PowerShell for additional processing. I don't need to see anything on the console so I wanted this to be hidden.

I want this for Beyond Compare, as I like the app a lot, but I really dislike how applications pollute our right-click context menu's with endless commands. I might only use Beyond Compare a couple of times per week, so I don't need it polluting my context menu for the 1,000+ other times per week that I right-click on something. I do this for all of my tools so that I have a very minimal and clean right-click context menu. The below would also apply to almost any other app that you would want a customised tooling in SendTo, and also almost unchanged for WinMerge if you prefer that tool (but Beyond Compare can also compare folders which can be very useful). For WinMerge, just break out of the script if a folder is selected at the first step as it cannot handle folders.

As I required two inputs, one from the initial SendTo, and the second from the OpenFileDialog or FolderBrowerDialog, this meant that I had to also pass the Argument given to the VBScript part of the solution. The syntax for that was a bit tricky to work out (it's 10+ years since I've had to use VBScript!), but is:

""" & path & """ """ & arg & """"

The solution then requires a .vbs launcher plus the .ps1 script and finally the shortcut in shell:sendto to call the scripts:

D:\MyPortableApps\ShortcutLauncher.vbs

Set oShell = CreateObject("WScript.Shell")
path = WScript.Arguments(0)
arg = WScript.Arguments(1)
PSCommand = "powershell -NoProfile -ExecutionPolicy ByPass -File """ & path & """ """ & arg & """"
oShell.run PScommand,0

D:\MyPortableApps\Compare with (Files or Folders, Beyond Compare).ps1

# Selected item in SendTo is the left side, then use OpenFileDialog or FolderBrowserDialog to pick the right side
$MyPrograms = "D:\MyPortableApps"   # Location of my portable apps
$left_side = (Get-Item $args[0]).FullName   # In case path contains '.' or '..'
$parent = Split-Path $left_side   # Use this as InitialDirectory
$IsFolder = $false; if ((Get-Item $left_side) -is [System.IO.DirectoryInfo]) { $IsFolder = $true }

Add-Type -AssemblyName System.Windows.Forms   # Required to access the OpenFileDialog object
if ($IsFolder) {
    $FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog   # -Property @{ InitialDirectory = $parent }
    $FolderBrowser.RootFolder = $parent
    $FolderBrowser.Description = "Select Folder to compare to '$left_side':"
    $Show = $FolderBrowser.ShowDialog()
    if ($Show -eq "OK") {
        $right_side = $FolderBrowser.SelectedPath
    } else {
        break
    }
} else {
    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{ InitialDirectory = $parent }   # [Environment]::GetFolderPath('Desktop')
    $FileBrowser.Title = "Select File to compare to '$left_side':"
    $null = $FileBrowser.ShowDialog()   # Assign to null as $FileBrowser does not return useful information by itself
    $right_side = $FileBrowser.FileName
}
# $ButtonClicked = [System.Windows.Forms.MessageBox]::Show("Beyond Compare will be opened with the following panes:`n`nLeft side: '$left_side'`n`nRight side: '$right_side'", 'Open Beyond Compare?', 'OKCancel')

$appexe_pf = "C:\Program Files\Beyond Compare 4\BCompare.exe"
$appexe_sb = "$MyPrograms\Beyond Compare 4\BCompare.exe"
if ( (!(Test-Path $appexe_sb)) -and (!(Test-Path $appexe_pf))) { choco install beyondcompare -y }
if (Test-Path $appexe_pf) { 
    & $appexe_pf "$left_side" "$right_side"
} else {
    & $appexe_sb "$left_side" "$right_side"
}

Snippet to create a shortcut in SendTo

function New-Shortcut ($mylnk, $mytgt, $myarg, $mywrk, $myico) {
    $lnk = $WScriptShell.CreateShortcut($mylnk)
    $lnk.TargetPath = $mytgt
    if ($myarg -ne "") { $lnk.Arguments = $myarg }
    if ($mywrk -ne "") { $lnk.WorkingDirectory = $mywrk }
    if ($myico -ne "") { $lnk.IconLocation = $myico }
    $lnk.Save()
}

$SendTo = "$Env:AppData\Microsoft\Windows\SendTo"
$lnkName = "Compare with (Files or Folders, Beyond Compare)"
$SendToLnk = "$SendTo\$lnkName.lnk"
$wscript = "C:\Windows\system32\wscript.exe"
New-Shortcut $SendToLnk $wscript "`"D:\MyPortableApps\ShortcutLauncher.vbs`" `"D:\MyPortableApps\$lnkName.ps1`"" "" ""
YorSubs
  • 3,194
  • 7
  • 37
  • 60
  • 1
    Nicely done, one thing to note, `[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")` is not needed in your code because `Add-Type -AssemblyName System.Windows.Forms` is ran before it. Also worth noting, `Add-Type` is the recommended way to load assemblies. – Santiago Squarzon Oct 25 '22 at 13:42
  • That's great, thanks, I've updated it with that. One other things doesn't work, weirdly, neither `.InitialDirectory` or `.RootFolder` actually seem to work with FolderBrowserDialog (though do work ok with OpenFileDialog); Folder Browser fails to open at the initial or root location that I define. – YorSubs Oct 25 '22 at 14:13
  • 1
    You can initialize instances like this, much easier `$FolderBrowser = [System.Windows.Forms.FolderBrowserDialog]@{ InitialDirectory = 'C:\path\to\initialfolder' }` // this should work fine too (works for me as long as it is a valid folder path – Santiago Squarzon Oct 25 '22 at 14:19