Batch + Powershell file chooser - InitialDirectory - powershell

I get a file chooser from another discussion and i want to change InitialDirectory value. Now is:
<# :
...
setlocal
for /f "delims=" %%I in ('powershell -noprofile "iex (${%~f0} | out-string)"')
...
goto :EOF
: #>
Add-Type -AssemblyName System.Windows.Forms
$f = new-object Windows.Forms.OpenFileDialog
$f.InitialDirectory = pwd
$f.Filter = "ucsdb backup (ucsdb.*)|ucsdb.*"
$f.ShowHelp = $false
$f.Multiselect = $false
[void]$f.ShowDialog()
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }
and it open the current directory but i want to open a subfolder. How can i write Batch "%cd%\UCSM_Files\" in InitialDirectory?
Sorry for my bad english

You can do it several ways, really. The shortest way would be to evaluate pwd within $() in a string like this:
$f.InitialDirectory = "$(pwd)\UCSM_Files"
Or you could use string formatting.
$f.InitialDirectory = "{0}\UCSM_Files" -f (pwd)

From the variable syntax it looks like you are trying to do this in PowerShell already. If so, you could use the Join-Path cmdlet (and the $PWD automatic variable):
$f.InitialDirectory = Join-Path -Path $PWD -ChildPath UCSM_Files

You could try setting an environment variable in your batch then use it in powershell:
<# :
...
set "initialdir=%~dp0"
...
#>
$f.InitialDirectory = "$($env:initialdir)UCSM_Files"
...
I also noticed you disable multiselect but then check for it anyway:
$f.Multiselect = $false
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }

Related

Make a PowerShell script silent when called from a shortcut

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?
Only way I know about to ensure this is using vbscript 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.
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`"" "" ""

Batch/VBScript/Powershell hybrid script for creating shortcut to shell folders

As stated in Q-title, I am trying to create and automate script for creating shortcuts to Shell-Special folders which doesn't have a valid path per path-string validation rules.
I have tried both Batch/VBScript hybrid and Powershell script for creating shortcut to Target path explorer shell:AppsFolder which when clicked will open that path exactly and not the basic ThisPC or FileExplorer path.
First the Batch/VBScript hybrid that failed:
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set /p "Esc_LinkName=Name: "
set /p "Esc_LinkTarget=Target: "
SET cSctVBS=CreateFolderShortcut.vbs
SET LOG=".\%~N0_runtime.log"
((
echo Set oWS = WScript.CreateObject^("WScript.Shell"^)
echo sLinkFile = oWS.ExpandEnvironmentStrings^("%USERPROFILE%\Desktop\!Esc_LinkName!"^)
echo Set oLink = oWS.CreateShortcut^(sLinkFile^)
:: echo oLink.TargetPath = oWS.ExpandEnvironmentStrings^("!Esc_LinkTarget!"^)
echo oLink.TargetPath = "!Esc_LinkTarget!"
echo oLink.Save
)1>!cSctVBS!
cscript //nologo .\!cSctVBS!
DEL !cSctVBS! /f /q
)1>>!LOG! 2>>&1
SETLOCAL DISABLEDELAYEDEXPANSION && ENDLOCAL
pause && goto :eof
which throws error: CreateFolderShortcut.vbs(4, 1) Microsoft VBScript runtime error: Invalid procedure call or argument on this line echo oLink.TargetPath = "!Esc_LinkTarget!" because it can't take target-path explorer shell:AppsFolder.
And now the Powershell script that failed:
$shell = New-Object -ComObject WScript.Shell
$destination = "$env:USERPROFILE\Desktop"
$shortcutPath = Join-Path -Path $destination -ChildPath 'My Apps Folder.lnk'
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = $shell.SpecialFolders.Item("AppsFolder")
$shortcut.Save()
which creates a shortcut to FileExplorer path instead.
Anyone can suggest, how to make this work to get what I want ?
It's definitely worth sticking with a PowerShell script (.ps1), as it doesn't require any trickery (though note that .ps1 files aren't directly executable from outside a PowerShell session):
Replace:
$shortcut.TargetPath = $shell.SpecialFolders.Item("AppsFolder")
with:
$shortcut.TargetPath = 'shell:AppsFolder'
Complete code:
$shell = New-Object -ComObject WScript.Shell
$destination = "$env:USERPROFILE\Desktop"
$shortcutPath = Join-Path -Path $destination -ChildPath 'My Apps Folder.lnk'
$shortcut = $shell.CreateShortcut($shortcutPath)
$shortcut.TargetPath = 'shell:AppsFolder'
$shortcut.Save()

How does one execute a powershell script from another powershell script, with capability to wait and get error codes?

I need to call a powershell script from another powershell script. The powershell script in question, myrobocopy.ps1, is a generalised module, just tuned to our specific needs. I need to call this from another script and get the error code it returns.
Attempts:
I tried it with Invoke-Expression, like this:
$source = "C:/sourcefolder"
$destination = "C:/destinationfolder"
$filename = "filename /LOG+:log.txt"
$arguments = "-source $source -destination $destination -file $filename"
$robocopyscript = "myrobocopy.ps1"
Invoke-Expression "$robocopyscript $arguments"
if ($LASTEXITCODE -ne 1){
$problems = 1
}
I think the issue with this is that we can't force the process to wait until myrobocopy.ps1 is finished. Additionally, if I'm honest with you, I don't even think $LASTEXITCODE works here, since Invoke-Expression isn't some global error handling tool. I'm new to Powershell, so yeah, its dumb, just showing my work here.
Next attempt looked like this:
$robocopy = "C:\testfolder\myrobocopy.ps1"
$source = "C:\testfolder"
$destination = "C:\testdestination"
$filename = "test.bat"
$arguments = "-source $source -destination $destination -file $filename"
$p = Start-Process $robocopy $arguments -Passthru -Wait
$code = $p.ExitCode
For all I know, this should work, yet when this script runs, it just opens myrobocopy.ps1 in notepad. I've seen some other answers regarding how to associate ps1 files with powershell itself, but I don't believe (could be mistaken here) that is the issue here.
Finally, I read that I need to actually start powershell itself, and then call the ps script. I'm not really sure how to do that, so I tried the following:
$robocopy = "C:\testfolder\myrobocopy.ps1"
$source = "C:\testfolder"
$destination = "C:\testdestination"
$filename = "test.bat"
$arguments = "-source $source -destination $destination -file $filename"
$p = Start-Process "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -File $robocopy -ArgumentList $arguments -PassThru -Wait
$code = $p.ExitCode
This returns the following error: Start-Process : A positional parameter cannot be found that accepts argument 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
So yeah I'm slightly lost. I'm sure I just don't know the syntax on how to do it. Help would be greatly appreciated, thank you!
EDIT: Just to add more info, myrobocopy.ps1 accepts 3 string parameters and explicitly returns the $LASTEXITCODE after running robocopy.
ANSWER:
Using the call operator made this work, and you use $LASTEXITCODE to retrieve the exit code.
$robocopy = "C:\testfolder\myrobocopy.ps1"
$source = "C:\testfolder"
$destination = "C:\testdestination"
$filename = "test.bat"
& $robocopy $source $destination $filename
Write-Output $LASTEXITCODE
If your "C:\testfolder\myrobocopy.ps1" put the result code on the pipeline, the call operator should do the trick:
$result = & "C:\testfolder\myrobocopy.ps1"
Another way to run robocopy and get the correct exit code would be to use Invoke-Expression with $global:LASTEXITCODE:
Function Start-Robocopy {
Param (
[Parameter(Mandatory)]
[String]$Source,
[Parameter(Mandatory)]
[String]$Destination,
[Parameter(Mandatory)]
[String]$Switches,
[String]$File
)
Try {
$result = [PSCustomObject]#{
Source = $Source
Destination = $Destination
File = $File
Switches = $Switches
RobocopyOutput = $null
ExitCode = $null
Error = $null
}
$global:LASTEXITCODE = 0 # required to get the correct exit code
$expression = [String]::Format(
'ROBOCOPY "{0}" "{1}" {2} {3}',
$Source, $Destination, $File, $Switches
)
$result.RobocopyOutput = Invoke-Expression $expression
$result.ExitCode = $LASTEXITCODE
}
Catch {
$result.Error = $_
}
Finally {
$result
}
}
$startParams = #{
Source = '\\contoso\FolderA'
Destination = '\\contoso\FolderA'
Switches = '/MIR /Z /R:3 /W:10 /NP /MT:16'
}
Start-Robocopy #startParams

How do I plug in a file I select in a powershell file selector gui?

I found a powershell script to open up a gui filepicker now how do I get the file I pick in it to be plugged into a variable? Also I have a program called binsmp that replaces hex in files from the command line how would I plug the file into that?
#echo off
setlocal
for /f "delims=" %%I in ('powershell -noprofile "iex (${%~f0} | out-string)"') do (
echo You chose %%~I
)
goto :EOF
Add-Type -AssemblyName System.Windows.Forms
$f = new-object Windows.Forms.OpenFileDialog
$f.InitialDirectory = pwd
$f.Filter = "Roms (*.sfc;*.smc)|*.sfc;*.smc|All Files (*.*)|*.*"
$f.ShowHelp = $false
$f.Multiselect = $false
[void]$f.ShowDialog()
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }
binsmp filename -paste paste.txt
Assuming that the filename part of your binsmp invocation is where the actual filename is supposed to be, give this a try:
<# :
:: launches a File... Open sort of file chooser and outputs choice(s) to the console
:: https://stackoverflow.com/a/15885133/1683264
#setlocal
#echo off
for /f "delims=" %%I in ('powershell -noprofile "iex (${%~f0} | out-string)"') do (
binsmp %%~I -paste paste.txt
)
goto :EOF
: end Batch portion / begin PowerShell hybrid chimera #>
Add-Type -AssemblyName System.Windows.Forms
$f = new-object Windows.Forms.OpenFileDialog
$f.InitialDirectory = pwd
$f.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
$f.ShowHelp = $true
$f.Multiselect = $true
[void]$f.ShowDialog()
if ($f.Multiselect) { $f.FileNames } else { $f.FileName }
You broke it when you removed the totally non-standard/supported powershell comment block around the actual cmd script code.

Using the Filenames method of System.Windows.Forms.OpenFileDialog returns System.String[]

I'm new to Powershell and I am struggling with creating a script that'll return the file(s) chosen to the batch script I'm writing.
It's suppose to, when launched, open up a file browser where you can select multiple files and when the user confirms the selection, returns the file paths to the batch script.
This is what's in the batch file.
call :createPSscript
powershell -noprofile -noninteractive -executionpolicy unrestricted -Command "%~dp0FileSelector.ps1" >status
del FileSelector.ps1 >nul 2>&1
set /p status=<status
set /p queue=<queue
del status >nul 2>&1
echo %queue%
echo %status%
pause
:createPSscript
echo Add-Type -AssemblyName System.Windows.Forms>FileSelector.ps1
echo $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog>>FileSelector.ps1
echo $openFileDialog.Title = "Select APK(s) to work with.">>FileSelector.ps1
echo $openFileDialog.InitialDirectory = [Environment]::GetFolderPath("Desktop")>>FileSelector.ps1
echo $openFileDialog.Filter = "Android App Package (*.apk)|*.apk|All files (*.*)|*.*">>FileSelector.ps1
echo $openFileDialog.MultiSelect = $true>>FileSelector.ps1
echo $openFileDialog.ShowHelp = $true>>FileSelector.ps1
echo $openFileDialog.AutoUpgradeEnabled = $true>>FileSelector.ps1
echo $openFileDialog.ShowDialog() > $null>>FileSelector.ps1
echo $stream = [System.IO.StreamWriter] "%~dp0queue">>FileSelector.ps1
echo 1 ^| %% $stream.WriteLine^($openFileDialog.Filenames^)>>FileSelector.ps1
echo $stream.close^(^)>>FileSelector.ps1
exit /b
And in the powershell script that the batch outputs to.
Add-Type -AssemblyName System.Windows.Forms
$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$openFileDialog.Title = "Select APK(s) to work with."
$openFileDialog.InitialDirectory = [Environment]::GetFolderPath("Desktop")
$openFileDialog.Filter = "Android App Package (*.apk)|*.apk|All files (*.*)|*.*"
$openFileDialog.MultiSelect = $true
$openFileDialog.ShowHelp = $true
$openFileDialog.AutoUpgradeEnabled = $true
$openFileDialog.ShowDialog()
$stream = [System.IO.StreamWriter] "C:\Users\<redacted>\<redacted>\queue"
1 | % $stream.WriteLine($openFileDialog.Filenames)
$stream.close()
It works perfectly fine when I use $openFileDialog.Filename instead of $openFileDialog.Filename
%Status% returns if the user canceled or not and %queue% should return the list of files.
Did I do the syntax correctly?
Annnnnnd I'm an idiot. Turns out just by invoking $openFileDialog.Filenames, it'll return the selected files to the output.