[How to] Functions to obtain file selection OR folder selection in GUI in Powershell

Discussion in 'Scripting' started by sebus, Jul 30, 2017.

  1. sebus

    sebus MDL Guru

    Jul 23, 2008
    5,867
    1,766
    180
    I needed to queue a list of files for processing (conversion to mp4 by external encoder, driven by SQL). So first needed to be able to obtain such list & then act on it. And I needed all these to be available in GUI, so the end user (operator) CAN see what they are selecting

    A couple of nice functions to get:
    • file select dialog box in PS
    (then you can process that selection in any way that needs to be)

    Code:
    Function Get-FileName($initialDirectory) {
        [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
    
        $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
        $OpenFileDialog.initialDirectory = $initialDirectory
        $OpenFileDialog.Multiselect = $true
        $OpenFileDialog.filter = "AVI (*.avi)| *.avi|MP4 (*.mp4)| *.mp4|MKV (*.mkv)| *.mkv|WMV (*.wmv)| *.wmv"
        $OpenFileDialog.ShowDialog() | Out-Null
        $OpenFileDialog.filenames
        }
    $inputfile = Get-FileName "C:\"
    $files = get-childitem $inputfile
    }
    • folder selection (not natively available in PS), which I picked up from here
    Code:
    Function BuildDialog {
        $sourcecode = @"
    using System;
    using System.Windows.Forms;
    using System.Reflection;
    namespace FolderSelect
    {
        public class FolderSelectDialog
        {
            System.Windows.Forms.OpenFileDialog ofd = null;
            public FolderSelectDialog()
            {
                ofd = new System.Windows.Forms.OpenFileDialog();
                ofd.Filter = "Folders|\n";
                ofd.AddExtension = false;
                ofd.CheckFileExists = false;
                ofd.DereferenceLinks = true;
                ofd.Multiselect = false;
            }
            public string InitialDirectory
            {
                get { return ofd.InitialDirectory; }
                set { ofd.InitialDirectory = value == null || value.Length == 0 ? Environment.CurrentDirectory : value; }
            }
            public string Title
            {
                get { return ofd.Title; }
                set { ofd.Title = value == null ? "Select a folder" : value; }
            }
            public string FileName
            {
                get { return ofd.FileName; }
            }
            public bool ShowDialog()
            {
                return ShowDialog(IntPtr.Zero);
            }
            public bool ShowDialog(IntPtr hWndOwner)
            {
                bool flag = false;
    
                if (Environment.OSVersion.Version.Major >= 6)
                {
                    var r = new Reflector("System.Windows.Forms");
                    uint num = 0;
                    Type typeIFileDialog = r.GetType("FileDialogNative.IFileDialog");
                    object dialog = r.Call(ofd, "CreateVistaDialog");
                    r.Call(ofd, "OnBeforeVistaDialog", dialog);
                    uint options = (uint)r.CallAs(typeof(System.Windows.Forms.FileDialog), ofd, "GetOptions");
                    options |= (uint)r.GetEnum("FileDialogNative.FOS", "FOS_PICKFOLDERS");
                    r.CallAs(typeIFileDialog, dialog, "SetOptions", options);
                    object pfde = r.New("FileDialog.VistaDialogEvents", ofd);
                    object[] parameters = new object[] { pfde, num };
                    r.CallAs2(typeIFileDialog, dialog, "Advise", parameters);
                    num = (uint)parameters[1];
                    try
                    {
                        int num2 = (int)r.CallAs(typeIFileDialog, dialog, "Show", hWndOwner);
                        flag = 0 == num2;
                    }
                    finally
                    {
                        r.CallAs(typeIFileDialog, dialog, "Unadvise", num);
                        GC.KeepAlive(pfde);
                    }
                }
                else
                {
                    var fbd = new FolderBrowserDialog();
                    fbd.Description = this.Title;
                    fbd.SelectedPath = this.InitialDirectory;
                    fbd.ShowNewFolderButton = false;
                    if (fbd.ShowDialog(new WindowWrapper(hWndOwner)) != DialogResult.OK) return false;
                    ofd.FileName = fbd.SelectedPath;
                    flag = true;
                }
                return flag;
            }
        }
        public class WindowWrapper : System.Windows.Forms.IWin32Window
        {
            public WindowWrapper(IntPtr handle)
            {
                _hwnd = handle;
            }
            public IntPtr Handle
            {
                get { return _hwnd; }
            }
    
            private IntPtr _hwnd;
        }
        public class Reflector
        {
            string m_ns;
            Assembly m_asmb;
            public Reflector(string ns)
                : this(ns, ns)
            { }
            public Reflector(string an, string ns)
            {
                m_ns = ns;
                m_asmb = null;
                foreach (AssemblyName aN in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
                {
                    if (aN.FullName.StartsWith(an))
                    {
                        m_asmb = Assembly.Load(aN);
                        break;
                    }
                }
            }
            public Type GetType(string typeName)
            {
                Type type = null;
                string[] names = typeName.Split('.');
    
                if (names.Length > 0)
                    type = m_asmb.GetType(m_ns + "." + names[0]);
    
                for (int i = 1; i < names.Length; ++i) {
                    type = type.GetNestedType(names[i], BindingFlags.NonPublic);
                }
                return type;
            }
            public object New(string name, params object[] parameters)
            {
                Type type = GetType(name);
                ConstructorInfo[] ctorInfos = type.GetConstructors();
                foreach (ConstructorInfo ci in ctorInfos) {
                    try {
                        return ci.Invoke(parameters);
                    } catch { }
                }
    
                return null;
            }
            public object Call(object obj, string func, params object[] parameters)
            {
                return Call2(obj, func, parameters);
            }
            public object Call2(object obj, string func, object[] parameters)
            {
                return CallAs2(obj.GetType(), obj, func, parameters);
            }
            public object CallAs(Type type, object obj, string func, params object[] parameters)
            {
                return CallAs2(type, obj, func, parameters);
            }
            public object CallAs2(Type type, object obj, string func, object[] parameters) {
                MethodInfo methInfo = type.GetMethod(func, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                return methInfo.Invoke(obj, parameters);
            }
            public object Get(object obj, string prop)
            {
                return GetAs(obj.GetType(), obj, prop);
            }
            public object GetAs(Type type, object obj, string prop) {
               PropertyInfo propInfo = type.GetProperty(prop, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                return propInfo.GetValue(obj, null);
            }
            public object GetEnum(string typeName, string name) {
                Type type = GetType(typeName);
                FieldInfo fieldInfo = type.GetField(name);
                return fieldInfo.GetValue(null);
            }
        }
    }
    "@
        $assemblies = ('System.Windows.Forms', 'System.Reflection')
        Add-Type -TypeDefinition $sourceCode -ReferencedAssemblies $assemblies -ErrorAction STOP
    }
    Much nicer is to convert it to a module as per this
    so it only needs to be imported in your script like

    Code:
    Import-Module BuildDialog
    
    BuildDialog
        $fsd = New-Object FolderSelect.FolderSelectDialog
        $fsd.Title = "What to select";
        $fsd.ShowDialog() | Out-Null
    
    $watch_path = $fsd.FileName
    $files = Get-ChildItem -Include @("*.avi", "*.mkv", "*.mp4", "*.wmv") -Path $watch_path -Recurse;
    
    Then you can work on either (file selection or folder selection) and process it in any way needed

    For a user presented selection to chose from, I used solution from here by Martin Brandl (I am sure there are many many more way to do it, but this one worked well for me & is simple)

    Code:
    # define a task list with the messages to display and the functions to invoke:
    $taskList = @(
        [PSCustomObject]@{Message = 'File Selection'; Task = { FileSelect }}
        [PSCustomObject]@{Message = 'Folder Selection'; Task = { FolderSelect }}
      )
    
    # let the user pick a task:
    Write-Host -ForegroundColor Yellow "Choose a task:"
    
    $taskList | foreach -Begin { $i = 1;} -Process {
        Write-Host -ForegroundColor Yellow ('{0}. {1}' -f ($i++), $_.Message)
    }
    
    do
    {
        $value = Read-Host 'Choose a number from the task list'
    
    }
    while($value -match '\D+' -or $value -le 0 -or $value -gt $taskList.Count)
    
    # invoke the task:
    & $taskList[$value-1].Task
    sebus
     
  2. bkul

    bkul MDL Member

    Apr 2, 2015
    133
    37
    10
    #2 bkul, Nov 5, 2017
    Last edited: Nov 5, 2017
    I've come here searching for any PShell-scripting branch on the MDL as I've detected strange PShell fs CMDLETs behaviour when handling files which names contains '['-character. 1st of all Test-Path -IsValid answers that there is no problem with such a file:
    Code:
    PS > Test-Path "V:\GBooks\Iskra zhizni [pierievod R.Eivadisa] - Erikh Mariia Riemark.zip" -IsValid
    True
    
    But Get-Item, Get-Content, Set-Content, Expand-Archive,... fails if file name contains "["-character.:
    Code:
    PS E:\@Projects\BuildsHistory\@ProWS-RS4> Test-Path ".\_[z].txt"
    False
    
    PS E:\@Projects\BuildsHistory\@ProWS-RS4> "content of the file _[z].txt">"_[z].txt"
    out-file : Cannot perform operation because the wildcard path _[z].txt did not resolve to a file.
    At line:1 char:1
    + "content of the file _[z].txt">"_[z].txt"
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OpenError: (_[z].txt:String) [Out-File], FileNotFoundException
        + FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.OutFileCommand
    
    PS E:\@Projects\BuildsHistory\@ProWS-RS4> "content of the file _[z].txt"| Set-Content "_[z].txt"
    Set-Content : An object at the specified path _[z].txt does not exist, or has been filtered by the -Include or -Exclude parameter.
    At line:1 char:33
    + "content of the file _[z].txt"| Set-Content "_[z].txt"
    +                                 ~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (System.String[]:String[]) [Set-Content], Exception
        + FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.SetContentCommand
    
    PS E:\@Projects\BuildsHistory\@ProWS-RS4> get-content ".\_[z].txt"
    get-content : An object at the specified path .\_[z].txt does not exist, or has been filtered by the -Include or -Exclude parameter.
    At line:1 char:1
    + get-content ".\_[z].txt"
    + ~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
        + FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
    
    Simple test proves that test-file with a name "_[z].txt" exists and is not empty:
    PS E:\@Projects\BuildsHistory\@ProWS-RS4> get-content ".\_*z].txt"
    "content of the file _[z].txt"

    Question: IS THIS BEHAVIOUR WELL KNOWN or this is a BUG? My environments are Windows 10 Stable, 16299.19 and Insider, 17025

    P.S: Sorry I haven't mentioned your code but as you see I am not ready to implement and evaluate it... :)
     
  3. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    315
    279
    10
    I realize this is a late reply, but why are you using the -IsValid switch? That only checks if the synax for the path is valid; it does not detect whether the files at that path location actually exist.