BrowseForFolders doesn't display on top - powershell

I'm writing PS Script and following block of code shows dialogbox below windows forms gui.
$btn1 = New-Object Windows.Forms.Button
$btn1.Text = "Wybierz folder projektowy"
$btn1.Location = New-Object System.Drawing.Point(170,140)
$btn1.Size = New-Object System.Drawing.Size(160,20)
$btn1.add_Click({
function Select-Folder($message='Select a folder', $path = 0) {
$object = New-Object -comObject Shell.Application
$object.topmost=$true
$folder = $object.BrowseForFolder(0, $message, 0, $path)
if ($folder -ne $null) {
$folder.self.Path
}
}
$folderPath = Select-Folder 'Select the folder where the move scripts reside'
If ($folderPath) {
Set-Content -Path "C:\Projekty\logs\temp_path.txt" -Value $folderPath.ToString() -Encoding Unicode
write-host $folderPath
get-content -Path "C:\Projekty\logs\temp_path.txt"
}
Else { Write-Host 'I do not have a folder path' }
})
$form_acl.Controls.Add($btn1)
Is there any way to make it display on top?
Here's screenshot of a problem:

You can try this alternative instead:
function Select-Folder {
[CmdletBinding()]
param (
# sets the descriptive text displayed above the tree view control in the dialog box
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Please select a directory.",
# sets the (pre)selected path
[Parameter(Mandatory=$false, Position=1)]
[string]$InitialDirectory,
# sets the root folder where the browsing starts from
[Parameter(Mandatory=$false)]
[System.Environment+SpecialFolder]$RootFolder = [System.Environment+SpecialFolder]::Desktop,
# sets a value indicating whether the 'New Folder' button appears in the folder browser dialog box
[switch]$ShowNewFolderButton
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Message
$dialog.SelectedPath = $InitialDirectory
$dialog.RootFolder = $RootFolder
$dialog.ShowNewFolderButton = $ShowNewFolderButton.IsPresent
$selected = $null
# force the dialog TopMost:
# because the owning window will not be used after the dialog has been closed,
# you can simply create a new form inside the method call.
$result = $dialog.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true; TopLevel = $true}))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$selected = $dialog.SelectedPath
}
# clear the FolderBrowserDialog from memory
$dialog.Dispose()
# return the selected folder
$selected
}
Select-Folder -Message 'Select the folder where the move scripts reside' -ShowNewFolderButton

Theo's helpful answer shows an alternative, WinForms-based way to invoke a folder-browsing dialog, via the System.Windows.Forms.FolderBrowserDialog class.
However, it seems that all that is missing from your original approach is to pass your form's window handle (hWND, .Handle) as the first argument to the Shell.Application COM object's .BrowseForFolder() method, which makes it the dialog's owner window and therefore shows the dialog on top of it - even if the form itself has the .TopMost property set:
$folder = (New-Object -ComObject Shell.Application).BrowseForFolder(
$form_acl.Handle, # Pass your form's window handle to make it the owner window
$message,
0,
$path)
Here's a simplified, self-contained example (requires PSv5+, but can be adapted to earlier versions):
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create a sample topmost form with a single
# button that invokes a folder-browsing dialog.
($form = [Form] #{
Text = "Topmost Form"
Size = [Size]::new(300, 100)
TopMost = $true # Make the form topmost.
StartPosition = 'CenterScreen'
}).Controls.AddRange(#(
($folderBrowseButton = [Button] #{
Location = [Point]::new(70, 20)
Size = [Size]::new(160,30)
Text = 'Browse for Folder'
})
))
$folderBrowseButton.add_Click({
$path = 'C:\'
# IMPORTANT: Pass $form.Handle, the form's window handle (HWND) as the first argument, which
# makes the form the owning window, ensuring that the dialog is displayed on
# top - even if the form itself is set to topmost.
$folder = (New-Object -ComObject Shell.Application).BrowseForFolder(
$form.Handle,
'Pick a target folder:',
0, # Options
$path # Starting directory path.
)
if ($null -ne $folder) {
Write-Verbose -vb ('Folder picked: ' + $folder.self.Path)
} else {
Write-Verbose -vb 'Folder browsing canceled.'
}
})
$null = $form.ShowDialog()

Related

Loading Data from .csv into DataGridView Form

I am trying to load the contents of a .csv table into a DataGridView. In the following code it is loaded at two point with the same method: after a button press and in the main routine.
The one in the main routine works, but the button doesn't work.
(The DataGrid just goes blank after pressing the button, so it apprears it's not a scope issue).
#############################################
#Functions
#############################################
function SelectCsvFile () {
Add-Type -AssemblyName System.Windows.Forms
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.Title = "Datei auswählen"
$OpenFileDialog.InitialDirectory = Get-Location
$OpenFileDialog.filter = 'Tabelle (*.csv)|*.csv'
$SelectedFile=$OpenFileDialog.ShowDialog()
If ($SelectedFile -eq "Cancel") {
$result = $null
}
else {
$result= new-object System.Collections.ArrayList
$data=#(import-csv $OpenFileDialog.FileName -Delimiter $ListImportDelimiter)
$result.AddRange($data)
}
$OpenFileDialog.Dispose()
return $result
}
#############################################
#Main Routine
#############################################
# Init PowerShell Gui
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# Create a new form
$ManageTeamsForm = New-Object system.Windows.Forms.Form
# Define the size, title and background color
$ManageTeamsForm.ClientSize = New-Object System.Drawing.Point(1000,700)
#DataGrid
$DataGrid = new-object System.windows.forms.DataGridView
$DataGrid.width = 800
$DataGrid.height = 500
$DataGrid.location = New-Object System.Drawing.Point(37,80)
#loades testing csv on startup
$result= new-object System.Collections.ArrayList
$data=#(import-csv "./testfile.csv" -Delimiter $ListImportDelimiter)
$result.AddRange($data)
$DataGrid.DataSource = $result
#Select File Button
$SelectCsvFileButton = New-Object System.Windows.Forms.Button
$SelectCsvFileButton.Location = New-Object System.Drawing.Size(100,35)
$SelectCsvFileButton.Size = New-Object System.Drawing.Size(120,23)
$SelectCsvFileButton.Text = "Open CSV"
$SelectCsvFileButton.Add_Click({
$DataGrid.DataSource =SelectCsvFile;
ForEach ($row in $DataGrid.DataSource ){ #Testing if DataSource successfully changes
write-host $row
}
})
$ManageTeamsForm.controls.AddRange(#($SelectCsvFileButton,$DataGrid))
[void]$ManageTeamsForm.ShowDialog()
Your code is correct the only issue is that the output from your function SelectCsvFile is being enumerated, your function is no longer outputting an ArrayList but an object[] or a single object PSCumstomObject.
This is more or less explained in about Script Blocks:
A script block returns the output of all the commands in the script block, either as a single object or as an array.
You can overcome this by using the comma operator , which will wrap your ArrayList in a single element array to avoid losing the output type.
To put it into perspective with a simple example:
(& { [System.Collections.ArrayList]#(0..10) }).GetType() # Object[]
(& { , [System.Collections.ArrayList]#(0..10) }).GetType() # ArrayList
So, changing your function as follows should solve your problem:
function SelectCsvFile {
$fileDialog = [System.Windows.Forms.OpenFileDialog]#{
Title = "Datei auswählen"
InitialDirectory = Get-Location
Filter = 'Tabelle (*.csv)|*.csv'
}
if($fileDialog.ShowDialog() -eq 'OK') {
, [System.Collections.ArrayList]#(Import-Csv $fileDialog.FileName -Delimiter $ListImportDelimiter)
}
}

How to enter get-childitem in to a string

I am tring to create a script that will output the name of .txt files via for loop that counts the number of files and creates an option to open .txt file from just one click.
$s = (Get-ChildItem -Path C:\*.txt -Name | Measure-Object -Line).Lines
for($i=0; $1 -gt 5 ;$i++)
{
$c = [string[]](Get-ChildItem -Path C:\*.txt -Name)
[void] $objListBox.Items.Add('$i')
Write-Output $c
}
I am stuck with Get-childitem like in $c to get the list of file names into a variable so i can split or get the line for user option.
Thanks in advance
i am not sure what exactly you want from the script,
but here is my approach to set the items in $objListBox
$txtFiles=Get-ChildItem -Path C:\stackoverflow -Recurse -Filter *.txt
#Option 1 FullPath to Items
$txtFiles.fullname | ForEach-Object { $objListBox.Items.Add($_) }
#Option 2 Just the Name to Items
$txtFiles.name | ForEach-Object { $objListBox.Items.Add($_) }
#Option 3 Just the Name without Extentension to Items
$txtFiles.basename | ForEach-Object { $objListBox.Items.Add($_) }
#Count for All Files
$txtFiles.Count
You can use the .ToString
method to convert any given object to a string. If you want $c to be a string, the code you are looking for would look something like this:
$c = (Get-ChildItem -Path C:\*.txt -Name).ToString
Made a form for you that is created with a dynamic size and additionally a scrollbar if there is no space left. buttons that start the txt file and close the form. More comments in the code.
$basePath = "C:\"
$SearchString = Join-Path $basePath "*.txt"
$filenames = #(Get-ChildItem -Path $SearchString -Name | Sort)
$count = $filenames.count
#All you need for System.Windows.Forms to work
Add-Type -AssemblyName System.Windows.Forms
# Variables for generating size
$ButtonHeight = 35
$ButtonWidth = 450
$WindowTitle = "Choose a file"
$BottomSpace = $StartHeight = 10
$LeftSpace = $RightSpace = 30
$CurrentHeight = $Startheight
$FormHeight = 60 + $BottomSpace + $StartHeight + $ButtonHeight * $count
$FormWidth = 20 + $LeftSpace + $RightSpace + $ButtonWidth
# Create the form
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$form.FormBorderStyle = "Fixed3d" # Sizeable: User may change size - Fixed3d: User may not change size
$form.StartPosition = "CenterScreen"
$Form.AutoScroll = $True # Scrollbars when you need it
$form.Topmost = $true #always on top
$form.MaximizeBox = $false #Allows to maximize window
# Generate the buttons in a foreach and arrange them
foreach ($filename in $filenames) {
$GeneratedButton = New-Object System.Windows.Forms.Button
$GeneratedButton.Location = New-Object System.Drawing.Size($LeftSpace,$CurrentHeight)
$GeneratedButton.Size = New-Object System.Drawing.Size($ButtonWidth,$ButtonHeight)
$GeneratedButton.Text = $filename
# Action to take when button is clicked -- Open file and close form
$GeneratedButton.Add_Click({ Start-Process (Join-Path $BasePath $this.text) ; $form.Close() })
$form.Controls.Add($GeneratedButton)
$CurrentHeight += $ButtonHeight
}
# Activate the Form when loaded
$form.Add_Load({
$form.Activate()
})
# Show the form when loaded, but hide any results
$form.ShowDialog() > $null # Trash any output

PowerScript: System.Windows.Forms.FolderBrowserDialog opening in the background

PowerScript Noob here.
I've found a snippet of code that lets the user select a folder thru a Folder Browser Dialog, instead of having enter the path to the folder manually.
Works as expected, except the Folder Browser Dialog often opens behind other windows on the screen, which is getting tiresome.
Here is the code:
Function Get-Folder($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null
$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.Description = "Select a folder"
$foldername.rootfolder = "MyComputer"
$foldername.SelectedPath = $initialDirectory
if($foldername.ShowDialog() -eq "OK")
{
$folder += $foldername.SelectedPath
}
return $folder
}
$FolderNavn = Get-Folder($StartFolder)
How do I get the Folder Browser Dialog to open 'on top of' all other Windows?
Thanks.
To set the BrowseForFolder dialog TopMost, you need to use the ShowDialog() overloaded method with a parameter that specifies the dialogs owner (parent) form.
The easiest I think it to just create a new Form with property Topmost set to $true and use that as owner form:
function Get-Folder {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Please select a directory.",
[Parameter(Mandatory=$false, Position=1)]
[string]$InitialDirectory,
[Parameter(Mandatory=$false)]
[System.Environment+SpecialFolder]$RootFolder = [System.Environment+SpecialFolder]::Desktop,
[switch]$ShowNewFolderButton
)
Add-Type -AssemblyName System.Windows.Forms
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Message
$dialog.SelectedPath = $InitialDirectory
$dialog.RootFolder = $RootFolder
$dialog.ShowNewFolderButton = if ($ShowNewFolderButton) { $true } else { $false }
$selected = $null
# force the dialog TopMost
# Since the owning window will not be used after the dialog has been
# closed we can just create a new form on the fly within the method call
$result = $dialog.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$selected = $dialog.SelectedPath
}
# clear the FolderBrowserDialog from memory
$dialog.Dispose()
# return the selected folder
$selected
}

Ask user for file path to save

This is what I was going for
$x = Get-Process
$y = Get-Date -Format yyyy-MM-dd_hh.mmtt
$SelPath = Read-Host -Prompt "Choose a location to save the file?"
$Path = $SelPath + 'Running Process' + ' ' + $FixedDate + '.txt'
$x | Out-File $Path
"Read-Host". For example:
$folder = Read-Host "Folder location"
While the links provided in the comments show the use of the FolderBrowserDialog, all of these fail to show it as a Topmost form and also do not dispose of the form when done.
Here's two functions that ensure the dialog gets displayed on top.
The first one uses the Shell.Application Com object:
# Show an Open Folder Dialog and return the directory selected by the user.
function Get-FolderName {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Select a directory.",
[string]$InitialDirectory = [System.Environment+SpecialFolder]::MyDocuments,
[switch]$ShowNewFolderButton
)
$browserForFolderOptions = 0x00000041 # BIF_RETURNONLYFSDIRS -bor BIF_NEWDIALOGSTYLE
if (!$ShowNewFolderButton) { $browserForFolderOptions += 0x00000200 } # BIF_NONEWFOLDERBUTTON
$browser = New-Object -ComObject Shell.Application
# To make the dialog topmost, you need to supply the Window handle of the current process
[intPtr]$handle = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle
# see: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773205(v=vs.85).aspx
$folder = $browser.BrowseForFolder($handle, $Message, $browserForFolderOptions, $InitialDirectory)
$result = $null
if ($folder) {
$result = $folder.Self.Path
}
# Release and remove the used Com object from memory
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($browser) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
return $result
}
$folder = Get-FolderName
if ($folder) { Write-Host "You selected the directory: $folder" }
else { "You did not select a directory." }
The second one uses a bit of C# code for the 'Topmost' feature and System.Windows.Forms
function Get-FolderName {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Please select a directory.",
[System.Environment+SpecialFolder]$InitialDirectory = [System.Environment+SpecialFolder]::MyDocuments,
[switch]$ShowNewFolderButton
)
# To ensure the dialog window shows in the foreground, you need to get a Window Handle from the owner process.
# This handle must implement System.Windows.Forms.IWin32Window
# Create a wrapper class that implements IWin32Window.
# The IWin32Window interface contains only a single property that must be implemented to expose the underlying handle.
$code = #"
using System;
using System.Windows.Forms;
public class Win32Window : IWin32Window
{
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
"#
if (-not ([System.Management.Automation.PSTypeName]'Win32Window').Type) {
Add-Type -TypeDefinition $code -ReferencedAssemblies System.Windows.Forms.dll -Language CSharp
}
# Get the window handle from the current process
# $owner = New-Object Win32Window -ArgumentList ([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or write like this:
$owner = [Win32Window]::new([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# Or use the the window handle from the desktop
# $owner = New-Object Win32Window -ArgumentList (Get-Process -Name explorer).MainWindowHandle
# Or write like this:
# $owner = [Win32Window]::new((Get-Process -Name explorer).MainWindowHandle)
Add-Type -AssemblyName System.Windows.Forms
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Message
$dialog.RootFolder = $InitialDirectory
# $dialog.SelectedPath = '' # a folder within the RootFolder to pre-select
$dialog.ShowNewFolderButton = if ($ShowNewFolderButton) { $true } else { $false }
$result = $null
if ($dialog.ShowDialog($owner).ToString() -eq 'OK') {
$result = $dialog.SelectedPath
}
# clear the FolderBrowserDialog from memory
$dialog.Dispose()
return $result
}
$folder = Get-FolderName -InitialDirectory MyPictures
if ($folder) { Write-Host "You selected the directory: $folder" }
else { "You did not select a directory." }
Hope that helps
p.s. See Environment.SpecialFolder Enum for the [System.Environment+SpecialFolder] enumeration values

Clearing variables after loop has run in powershell

I've got a script that runs well now, only when I run it the first time the folder check comes back as blank thus going to the fall back folder. Then if I run the script again and choose a different user, it will output the original selection. As a result, I'm always one user behind from the desired output.
I have tried clearing the variables, but I'm seeing that the variables are being clears before the script is run, thus causing it to be null.
I have tried these steps from here: How to clear variable content in powershell and http://community.idera.com/powershell/powertips/b/tips/posts/clearing-all-user-variables which is where the function at the top is from.
This is for users on Windows 7, so Powershell 2.0 is the limit.
Here are the script parts:
Function to clear the variables :
# Store all the start up variables so you can clean up when the script finishes.
function Get-UserVariable ($Name = '*') {
# these variables may exist in certain environments (like ISE, or after use of foreach)
$special = 'ps','psise','psunsupportedconsoleapplications', 'foreach', 'profile'
$ps = [PowerShell]::Create()
$null = $ps.AddScript('$null=$host;Get-Variable')
$reserved = $ps.Invoke() |
Select-Object -ExpandProperty Name
$ps.Runspace.Close()
$ps.Dispose()
Get-Variable -Scope Global | Where-Object Name -like $Name | Where-Object { $reserved -notcontains $_.Name } | Where-Object { $special -notcontains $_.Name } | Where-Object Name
}
Function to create the user output:
# create a select box for users
function mbSelectBox {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$mbForm = New-Object System.Windows.Forms.Form
$mbLabel.Text = "Select the user to output to:"
[void] $mbListBox.Items.Add( "User01" )
[void] $mbListBox.Items.Add( "User02" )
$mbSelectBoxResult = $mbForm.ShowDialog()
if( $mbSelectBoxResult -eq [System.Windows.Forms.DialogResult]::OK) {
$script:mbUser = $mbListBox.SelectedItem
}
}
Function to call the conversion:
# get the folder for conversion
function mbAudioConvert {
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles()
$mbFileBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
$mbFileBrowser.SelectedPath = "C:\folderwithaudio"
$mbFileBrowser.ShowNewFolderButton = $false
$mbFileBrowser.Description = "Select the folder with the audio which you wish to convert:"
$mbLoop = $true
while( $mbLoop ) {
if( $mbFileBrowser.ShowDialog() -eq "OK" ) {
$mbLoop = $false
$mbCount = 1
$mbFolder = ( $mbFileBrowser.SelectedPath )
$mbHasRaw = ( $mbFolder + "\RAW" )
$mbUserPath = ( "\\NETWORK\SHARE\" + $mbUser + "\WATCHFOLDER" )
# the output profile path
if( !( Test-Path -Path "$mbUserPath" ) ) {
if( !( Test-Path -Path "$mbHasRaw" ) ) {
New-Item -ItemType Directory -Force -Path "$mbHasRaw"
$mbOutPath = $mbHasRaw
}
} else {
$mbOutPath = $mbUserPath
}
# get user to select user output
mbSelectBox
foreach( $mbItem in $mbItemInc ) {
$mbCount++
# clear the user variable
if( $mbItemNo -eq $mbCount[-1] ) {
Get-UserVariable | Remove-Variable
Write-Output ( "cleared variables" )
}
}
}
}
# call to function
mbAudioConvert
You've got some fundamental issues here. Such as referencing $mbUser before it is defined ($mbUserPath = ( "\\NETWORK\SHARE\" + $mbUser + "\WATCHFOLDER" ) is 14 lines before your call to mbSelectBox. Also, keep your scope consistent. If you're going to define $script:mbUser then you should reference $script:mbUser. Better yet, if the purpose of the function is to pick a user, have the function output the user and capture that in a variable.
# create a select box for users
function mbSelectBox {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$mbForm = New-Object System.Windows.Forms.Form
$mbLabel.Text = "Select the user to output to:"
[void] $mbListBox.Items.Add( "User01" )
[void] $mbListBox.Items.Add( "User02" )
$mbSelectBoxResult = $mbForm.ShowDialog()
if( $mbSelectBoxResult -eq [System.Windows.Forms.DialogResult]::OK) {
$mbListBox.SelectedItem
}
}
Then you can just add a parameter to the second function that calls that right up front if the parameter isn't provided.
# get the folder for conversion
function mbAudioConvert {
Param($mbUser = $(mbSelectBox))
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles()