How to enable autocompletion while entering paths? - powershell

Back in cmd.exe, I used
set /P file=Enter path to the file:
to enter file path from console (if it wasn't supplied as a parameter), and I could press Tab to get path auto-completion. However, when I execute in Powershell
$file = Read-Host -Prompt "Enter path to the file"
then I cannot use Tab to get auto-completion, it just inserts a tabulation in the input. IS there a way to simulate the former behaviour?

I know, I know... not really an answer to your question directly, but still totally worth mentioning IMHO. Why ask the user to type out a path (and chance typos) when you can just pop up a Open File dialog box? Drop this function at the beginning of the script:
function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "All files (*.*)| *.*"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
Then when you need to get a file name and path you can just do $file = get-filename and be done with it. If you only want certain file types you can change the filter line to only allow the user to see certain kinds of files, or even a specific file name (i.e. you need them to locate 'computerlist.csv' on the hard drive or something, you can change the . in the filter to computerlist.csv).

Based on the idea of JG in SD, the version of the selection folder is given here.
function Get-FolderPath($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") > $null
$FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowserDialog.SelectedPath = $initialDirectory
$FolderBrowserDialog.ShowDialog() > $null
$FolderBrowserDialog.SelectedPath
}

Here is an updated (PSVersion 5.1 and newer) version of JG in SD's post:
function Get-FileName {
param
(
$initialDirectory
)
$null = Add-Type -AssemblyName System.windows.forms
$OpenFileDialog = New-Object -TypeName System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = 'All files (*.*)| *.*'
$null = $OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}

I resolved this by using cmd.exe. I could not find a way to capture the output directly without powershell somehow disabling command completion, so I had to use Invoke-Expression and a temp file to pass back the result.
Invoke-Expression 'cmd /v:on /c set /P file=Enter target path: `& if defined file echo !file! `> %TEMP%\temp.tmp'
$TargetPath = $null
If ( Test-Path -PathType Leaf "$ENV:TEMP\temp.tmp" ) { $TargetPath = (Get-Content "$ENV:TEMP\temp.tmp").Trim() 2>$null }
Remove-Item "$ENV:TEMP\temp.tmp" 2>$null

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`"" "" ""

How Do I Grab Text File Output and Pass Down Pipeline?

I've got an application that opens a winform and asks the user to input a PDF file. Because I can't read strings in PDF files easily, I need to convert it to a .txt. When the user clicks OK, the application does this.
The problem I'm having is now using the .txt file object and passing it to another command without knowing the name of it. When I try to pipe it to another command, it won't work because I don't have the path. I think this is because the output of conversion is the string "OK" and not the actual .txt file.
How can I convert the PDFs to text (I'm using Xpdf) and pass the converted file down the pipeline for further processing?
If the means I'm using is the problem, how can I accomplish this task another way?
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.StartPosition = 'CenterScreen'
$button = New-Object System.Windows.Forms.Button
$form.Controls.Add($button)
$button.Text = 'Get file'
$button.Location = '10,10'
$button.Add_Click({
$ofd = New-Object system.windows.forms.Openfiledialog
$ofd.Filter = 'PDFs (*.pdf)|*.pdf'
$script:filename = 'Not found'
if ($ofd.ShowDialog() -eq 'Ok') {
$script:filename = $textbox.Text = $ofd.FileName
}
})
$buttonOK = New-Object System.Windows.Forms.Button
$form.Controls.Add($buttonOK)
$buttonOK.Text = 'Ok'
$buttonOK.Location = '10,40'
$buttonOK.DialogResult = 'OK'
$textbox = New-Object System.Windows.Forms.TextBox
$form.Controls.Add($textbox)
$textbox.Location = '100,10'
$textbox.Width += 50
$form.ShowDialog()
$output = & "C:\Users\eakinsa\Desktop\Style Guide Report\Includes\bin32\pdftotext" $filename
$output |
Get-Location -OutVariable textFile |
Select-String -Path $textFile -Pattern ed
Per Ansgar:
I amended the lines last few lines to, for now, maintain the default functionality of pdftotext where it creates the file in the same directory with the same name, as with his suggestion, I could easily replace .pdf with .txt on the end of the file path, thereby having the flexibility to pass the correct file path to subsequent functions. That made it so I was able to search the text file.
& "C:\users\eakinsa\Desktop\Style Guide Report\Includes\bin32\pdftotext" $filename
$pdf = Get-Item $filename
$textfile = $filename -replace '\.pdf$', '.txt'
Select-String -Path $textfile -Pattern ed
When you run pdftotext with just the input PDF as argument it creates the output text file in the same directory with the same basename and the extension txt.
& pdftotext C:\temp\foo.pdf # creates C:\temp\foo.txt
So you can build the text file path like this:
$pdf = Get-Item $filename
$textfile = Join-Path $pdf.DirectoryName ($pdf.BaseName + '.txt')
or like this:
$textfile = $filename -replace '\.pdf$', '.txt'
Alternatively you can tell pdftotext where to create the output file:
$textfile = 'C:\some\where\bar.txt'
& pdftotext $filename $textfile # creates C:\some\where\bar.txt

PowerShell Use File Dialog OR User Entry for Computer List

When I write PowerShell scripts they are typically to be used against many computers. Since we use a text file with a list of computers, I write my scripts with the following function:
Write-host "Select Text/CSV File with List of Computers"
#Provides Dialog Box to select a file with list of computers. File must contain only 1 of each of the Computer name(s) per line
Function Get-OpenFile($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
$OpenFileDialog.ShowHelp = $true
}
$InputFile = Get-OpenFile
If($InputFile -eq "Cancel"){
Write-host "Canceled By User"
exit}
Else{
$Computers = #(get-content -path $InputFile)
}
If I'm writing a script to be used for a single computer, I use the following:
$computer = Read-Host "Enter hostname"
My question is, how can I write these scripts to do the following:
A) the Get-OpenFile command to parse the computer names
B) if the Get-OpenFile is canceled, have the script prompt for a computer name using Read-Host
C) if the Read-Host entry is empty, then cancel the script
Any ideas?
I would combine the two into a single function:
function Get-ComputerName
{
param(
$InitialDirectory = ([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments))
)
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = $InitialDirectory
$OpenFileDialog.ShowHelp = $true
$OpenFileDialog.Filter = 'Text files (*.txt)|*.txt|All files (*.*)|*.*'
# Show OpenFileDialog window and test result
if($OpenFileDialog.ShowDialog() -eq 'OK'){
# A file was picked, read it
$Computers = Get-Content -Path $OpenFileDialog.FileName
} else {
# No file chosen, prompt user
$Computers = #(Read-Host 'Enter hostname:')
if($Computers[0] -like '*,*'){
# Input contains comma, assume multiple names
$Computers = $Computers[0] -split ','
}
}
# Dispose of file dialog
if($OpenFileDialog){
$OpenFileDialog.Dispose()
}
# Return computer names
return $Computers
}

Powershell script making swf extension open with internet explorer

I am trying to figure out how to write a powershell script that will set all .swf extensions to open up on Internet Explorer. I was trying to do this with a command prompt similar to the example below. Unfornately my boss is requiring this to be done through powershell. Any help with this would be greatly appreciated since I have a txt file that will loop through about 400 computers and need to make these changes on.
CMD Way
C:\>ASSOC .swf
.swf=ShockwaveFlash.ShockwaveFlash
C:\>FTYPE ShockwaveFlash.ShockwaveFlash
ShockwaveFlash.ShockwaveFlash="C:\bin\FlashPlayer.exe" %1
What I am Trying:
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
cmd /c assoc .swf=InternetExplorer.Application
### Will the above line automatically install on every pc? ###
}
Any help with trying to insert how to change the FTYPE in powershell so that $computer can cycle through would be greatly appreciated!
ASSOC and FTYPE are CMD.exe built-in commands, not executables, which means they can only be run in the context of CMD. The easiest way to run them is to invoke CMD from PowerShell.
cmd /c assoc .swf
cmd /c ftype ShockwaveFlash.ShockwaveFlash
If you need a "pure" PowerShell implementation, then you need to go to the registry. ASSOC and FTYPE merely write to the registry under theHKEY_CLASSES_ROOT hive. PowerShell does not have a default PSDrive for HKCR:, but that hive is also accessible under HKLM:\Software\Classes.
$ext = '.swf'
$HKCR = 'HKLM:\Software\Classes'
$ftype = Get-ItemProperty -Path "$HKCR\$ext" | select -expand '(default)'
$commandLine = Get-ItemProperty -Path "$HKCR\$ftype\shell\open" | select -expand '(default)'
$commandLine
To update these values, you simply use Set-ItemProperty on the same path.
Set-ItemProperty -Path "$HKCR\$ext" -Name '(default)' -Value 'ShockwaveFlash.ShockwaveFlash'
This requires you to run with Admin privileges. This also assumes that the key already exists. If not, you will have to create it with New-Item
if (-not (Test-Path "$HKCR\$ext")) {
New-Item -Path "$HKCR\$ext"
}
However, if all you want to do is set .swf files to open in iexplore.exe, then retrieving the values is unnecessary, as is modifying the FTYPE key. You need only change the extension association to InternetExplorer.Application instead of ShockwaveFlash.ShockwaveFlash. The following full scripts will do this:
In Batch file:
assoc .swf=InternetExplorer.Application
In PowerShell:
cmd /c assoc .swf=InternetExplorer.Application
In "pure" PowerShell, by modifying the registry:
$key = "HKLM:\Software\Classes\.swf"
$defaultName = '(default)'
$newValue = 'InternetExplorer.Application'
if (-not (Test-Path $key)) {
New-Item -Path $key
}
Set-Itemproperty -Path $key -Name $defaultName -Value $newValue
Note that modifying the registry doesn't take effect immediately. You need to also send a WM_SETTINGCHANGE event, or simply restart explorer.exe (eg: by logging off). You can find code to send the event here, but usually this isn't a problem for automated scripts because they force the user to re-login anyway.

Powershell popup while file is being moved

When my script starts it moves a file from one directory to another. After the file is completely downloaded I launch an application.
This all works, but what I would like is a popup window to appear while the file is being moved (Large files).
When I debug my code once it hits the Move-Item Cmdlet it waits until that command is completed before it moves on. What I want to do is while the Move-Item Cmdlet is running, popup an information window.
I know how to do the popup and the Move-Item, I just don't know how to get it to work the way I want. Any ideas?
Popup code
#pop up window letting mechanic know we are waiting for the files to be downloaded before opeing the SMT application
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("The EAFR file is still being moved to the correct directory, please wait.",0,"SMT Status",0)
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
Move-Item -LiteralPath ($filePath) $MLMoveDir
One option is to use WinForms to display a Please Wait dialog, rather than a Popup that has to be dismissed by the user. Something like:
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object system.Windows.Forms.Form
$Label = New-Object System.Windows.Forms.Label
$Form.Controls.Add($Label)
$Label.Text = "Copying file, please wait."
$Label.AutoSize = $True
$Form.Visible = $True
$Form.Update()
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
Move-Item -LiteralPath ($filePath) $MLMoveDir
#Hide popup
$Form.Close()
So what you could do is start the Move-Item as a job, and then do a While((get-job "jobname").state -ne completed){do popup}. I would look something like this:
#Move-Item
$MLMoveDir = "C:\move\data\AutoUpload\"
$MoveJob = Start-Job -scriptblock {Move-Item -LiteralPath ($filePath) $MLMoveDir}
#Show Popup
While($movejob.state -ne "Completed"){
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("The EAFR file is still being moved to the correct directory, please wait.",1,"SMT Status",0)
}
That way the popup shows for 1 second, and if the move is still happening it shows it again. I don't know that it would even appear to disappear/re-show so it would likely be seamless.