哇,您可以在 PowerShell 中使用 C#!
环顾四周,我很羡慕每个人都在使用 C# 并利用我不知道如何在 PowerShell 中访问的很酷的功能。
我喜欢这种方法例如,它不依赖于旧版 API,并且具有针对不受支持的系统的后备方案。
然后我发现您可以在 PowerShell 中使用实际的 C#!
我将两者放在一起,稍微修改了代码,以便更容易从 PS 调用,并得出了一种相当轻量级、希望强大的方法来召唤用户可用的最佳文件夹浏览器对话框:
[Revised code below]
我很想听听关于整个方法的可靠性的意见。
无法访问某些参考或其他问题的可能性有多大?
不管怎样,我现在对这种方法很满意:)
编辑:PowerShell“核心”(pwsh.exe;较新的程序集)
So, as @mklement0评论中指出,积极开发电源外壳(以前(以及以后,为了可读性)称为“PowerShell Core”;与Windows PowerShellWindows 附带的)似乎对此不太适用。
在研究了 PS Core 无助地仅报告为“The type initializer for 'VistaDialog' threw an exception.
“,(并添加对System.ComponentModel.Primitives
),事实证明 PS Core 倾向于使用更新版本的System.Windows.Forms
,就我而言5.0.4.0
,它不包含类型FileDialogNative
,更不用说它的嵌套类型了IFileDialog
.
我试图强制它使用 Windows PS 引用的版本(4.0.0.0
),但它不会遵守。
好吧,我终于花了不少钱,让 PS Core 简单地使用默认对话框,这已经是我一开始想要的升级版了 ????
因此,除了引入带有可选参数的构造函数方法的概念变化之外,我还为失败的“Vista 对话框”添加了后备方案。
我没有检查 PS 或单个程序集和/或类/类型的版本,而是简单地将调用包装在try
/catch
block.
$path = $args[0]
$title = $args[1]
$message = $args[2]
$source = @'
using System;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
/// <summary>
/// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions
/// </summary>
#pragma warning disable 0219, 0414, 0162
public class FolderSelectDialog {
private string _initialDirectory;
private string _title;
private string _message;
private string _fileName = "";
public string InitialDirectory {
get { return string.IsNullOrEmpty(_initialDirectory) ? Environment.CurrentDirectory : _initialDirectory; }
set { _initialDirectory = value; }
}
public string Title {
get { return _title ?? "Select a folder"; }
set { _title = value; }
}
public string Message {
get { return _message ?? _title ?? "Select a folder"; }
set { _message = value; }
}
public string FileName { get { return _fileName; } }
public FolderSelectDialog(string defaultPath="MyComputer", string title="Select a folder", string message=""){
InitialDirectory = defaultPath;
Title = title;
Message = message;
}
public bool Show() { return Show(IntPtr.Zero); }
/// <param name="hWndOwner">Handle of the control or window to be the parent of the file dialog</param>
/// <returns>true if the user clicks OK</returns>
public bool Show(IntPtr? hWndOwnerNullable=null) {
IntPtr hWndOwner = IntPtr.Zero;
if(hWndOwnerNullable!=null)
hWndOwner = (IntPtr)hWndOwnerNullable;
if(Environment.OSVersion.Version.Major >= 6){
try{
var resulta = VistaDialog.Show(hWndOwner, InitialDirectory, Title, Message);
_fileName = resulta.FileName;
return resulta.Result;
}
catch(Exception){
var resultb = ShowXpDialog(hWndOwner, InitialDirectory, Title, Message);
_fileName = resultb.FileName;
return resultb.Result;
}
}
var result = ShowXpDialog(hWndOwner, InitialDirectory, Title, Message);
_fileName = result.FileName;
return result.Result;
}
private struct ShowDialogResult {
public bool Result { get; set; }
public string FileName { get; set; }
}
private static ShowDialogResult ShowXpDialog(IntPtr ownerHandle, string initialDirectory, string title, string message) {
var folderBrowserDialog = new FolderBrowserDialog {
Description = message,
SelectedPath = initialDirectory,
ShowNewFolderButton = true
};
var dialogResult = new ShowDialogResult();
if (folderBrowserDialog.ShowDialog(new WindowWrapper(ownerHandle)) == DialogResult.OK) {
dialogResult.Result = true;
dialogResult.FileName = folderBrowserDialog.SelectedPath;
}
return dialogResult;
}
private static class VistaDialog {
private const string c_foldersFilter = "Folders|\n";
private const BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private readonly static Assembly s_windowsFormsAssembly = typeof(FileDialog).Assembly;
private readonly static Type s_iFileDialogType = s_windowsFormsAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
private readonly static MethodInfo s_createVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags);
private readonly static MethodInfo s_onBeforeVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags);
private readonly static MethodInfo s_getOptionsMethodInfo = typeof(FileDialog).GetMethod("GetOptions", c_flags);
private readonly static MethodInfo s_setOptionsMethodInfo = s_iFileDialogType.GetMethod("SetOptions", c_flags);
private readonly static uint s_fosPickFoldersBitFlag = (uint) s_windowsFormsAssembly
.GetType("System.Windows.Forms.FileDialogNative+FOS")
.GetField("FOS_PICKFOLDERS")
.GetValue(null);
private readonly static ConstructorInfo s_vistaDialogEventsConstructorInfo = s_windowsFormsAssembly
.GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
.GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
private readonly static MethodInfo s_adviseMethodInfo = s_iFileDialogType.GetMethod("Advise");
private readonly static MethodInfo s_unAdviseMethodInfo = s_iFileDialogType.GetMethod("Unadvise");
private readonly static MethodInfo s_showMethodInfo = s_iFileDialogType.GetMethod("Show");
public static ShowDialogResult Show(IntPtr ownerHandle, string initialDirectory, string title, string description) {
var openFileDialog = new OpenFileDialog {
AddExtension = false,
CheckFileExists = false,
DereferenceLinks = true,
Filter = c_foldersFilter,
InitialDirectory = initialDirectory,
Multiselect = false,
Title = title
};
var iFileDialog = s_createVistaDialogMethodInfo.Invoke(openFileDialog, new object[] { });
s_onBeforeVistaDialogMethodInfo.Invoke(openFileDialog, new[] { iFileDialog });
s_setOptionsMethodInfo.Invoke(iFileDialog, new object[] { (uint) s_getOptionsMethodInfo.Invoke(openFileDialog, new object[] { }) | s_fosPickFoldersBitFlag });
var adviseParametersWithOutputConnectionToken = new[] { s_vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
s_adviseMethodInfo.Invoke(iFileDialog, adviseParametersWithOutputConnectionToken);
try {
int retVal = (int) s_showMethodInfo.Invoke(iFileDialog, new object[] { ownerHandle });
return new ShowDialogResult {
Result = retVal == 0,
FileName = openFileDialog.FileName
};
}
finally {
s_unAdviseMethodInfo.Invoke(iFileDialog, new[] { adviseParametersWithOutputConnectionToken[1] });
}
}
}
// Wrap an IWin32Window around an IntPtr
private class WindowWrapper : IWin32Window {
private readonly IntPtr _handle;
public WindowWrapper(IntPtr handle) { _handle = handle; }
public IntPtr Handle { get { return _handle; } }
}
public string getPath(){
if (Show()){
return FileName;
}
return "";
}
}
'@
Add-Type -Language CSharp -TypeDefinition $source -ReferencedAssemblies ("System.Windows.Forms", "System.ComponentModel.Primitives")
([FolderSelectDialog]::new($path, $title, $message)).getPath()
这应该适用于Windows PowerShell(最终版本~5.1
)和当前的 PS“核心”(pwsh.exe
~7.1.3
)