/// <summary> /// Gets file icon as <b>Bitmap</b>. /// Returns null if the icon is not cached and failed to get it, eg file does not exist. /// </summary> /// <param name="file">Any file or folder.</param> /// <param name="useExt"> /// Get file type icon, depending on filename extension. Use this to avoid getting separate image object for each file of same type. /// This is ignored if filename extension is ".ico" or ".exe" or starts with ".exe," or ".dll,". /// </param> /// <param name="giFlags">Flags for <see cref="AIcon.GetFileIconImage"/>.</param> /// <param name="autoUpdate"> /// If not null, the cached image will be auto-updated when changed. Then will be called this function. It can update the image in UI. /// How it works: If this function finds cached image, it sets timer that after ~50 ms loads that icon/image from file again and compares with the cached image. If different, updates the cache. Does it once, not periodically. /// Use only in UI threads. Does not work if this thread does not retrieve/dispatch posted messages. /// </param> /// <param name="auParam">Something to pass to the <i>autoUpdate</i> callback function.</param> /// <remarks> /// If the icon is in the memory cache, gets it from there. /// Else if it is in the file cache, gets it from there and adds to the memory cache. /// Else gets from file (uses <see cref="AIcon.GetFileIconImage"/> and adds to the file cache and to the memory cache. /// </remarks> public Bitmap GetImage(string file, bool useExt, IconGetFlags giFlags = 0, Action <Bitmap, object> autoUpdate = null, object auParam = null) { if (useExt) { var ext = APath.GetExtension(file); if (ext.Length == 0) { if (AFile.ExistsAsDirectory(file)) { ext = file; } else { ext = ".no-ext"; } } else { //ext = ext.Lower(); if (ext.Eqi(".ico") || ext.Eqi(".exe") || ext.Starts(".exe,", true) || ext.Starts(".dll,", true)) { ext = file; } } file = ext; } else if (APath.IsFullPathExpandEnvVar(ref file)) { file = APath.Normalize_(file, noExpandEV: true); } return(_GetImage(file, giFlags, null, autoUpdate, auParam, true)); }
/// <summary> /// Gets file path extension like ".txt" or URL protocol like "http". /// Returns null if path does not end with ".extension" and does not start with "protocol:"; also if starts with "shell:". /// </summary> /// <param name="path">File path or URL. Can be just extension like ".txt" or protocol like "http:".</param> /// <param name="isProtocol">Receives true if URL or protocol.</param> internal static string GetExtensionOrProtocol(string path, out bool isProtocol) { isProtocol = false; if (path.NE()) { return(null); } if (!PathIsExtension(path)) { int i = path.IndexOf(':'); if (i > 1) { path = path.Remove(i); //protocol if (path == "shell") { return(null); //eg "shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App" } isProtocol = true; } else { path = APath.GetExtension(path); if (path.NE()) { return(null); } } } return(path); }
//this is ~300 times slower than AFile.Move. SHFileOperation too. Use only for files or other shell items in virtual folders. Unfinished. public static void RenameFileOrDirectory(string path, string newName) { APerf.First(); if (APath.IsInvalidFileName(newName)) { throw new ArgumentException("Invalid filename.", nameof(newName)); } path = _PreparePath(path, nameof(path)); APerf.Next(); var si = _ShellItem(path, "*rename"); APerf.Next(); var fo = new Api.FileOperation() as Api.IFileOperation; APerf.Next(); try { fo.SetOperationFlags(4); //FOF_SILENT. Without it shows a hidden dialog that becomes the active window. AuException.ThrowIfFailed(fo.RenameItem(si, newName, null), "*rename"); APerf.Next(); AuException.ThrowIfFailed(fo.PerformOperations(), "*rename"); APerf.Next(); } finally { Api.ReleaseComObject(fo); Api.ReleaseComObject(si); } APerf.NW(); }
/// <summary> /// Gets process executable file name (like "notepad.exe") or full path. /// Returns null if fails. /// </summary> /// <param name="processId">Process id.</param> /// <param name="fullPath"> /// Get full path. /// Note: Fails to get full path if the process belongs to another user session, unless current process is running as administrator; also fails to get full path of some system processes. /// </param> /// <param name="noSlowAPI">When the fast API QueryFullProcessImageName fails, don't try to use another much slower API WTSEnumerateProcesses. Not used if <i>fullPath</i> is true.</param> /// <remarks> /// This function is much slower than getting window name or class name. /// </remarks> /// <seealso cref="AWnd.ProgramName"/> /// <seealso cref="AWnd.ProgramPath"/> /// <seealso cref="AWnd.ProcessId"/> public static string GetName(int processId, bool fullPath = false, bool noSlowAPI = false) { if (processId == 0) { return(null); } string R = null; //var t = ATime.PerfMicroseconds; //if(s_time != 0) AOutput.Write(t - s_time); //s_time = t; using var ph = Handle_.OpenProcess(processId); if (!ph.Is0) { //In non-admin process fails if the process is of another user session. //Also fails for some system processes: nvvsvc, nvxdsync, dwm. For dwm fails even in admin process. //getting native path is faster, but it gets like "\Device\HarddiskVolume5\Windows\System32\notepad.exe" and I don't know API to convert to normal if (_QueryFullProcessImageName(ph, !fullPath, out var s)) { R = s; if (APath.IsPossiblyDos_(R)) { if (fullPath || _QueryFullProcessImageName(ph, false, out s)) { R = APath.ExpandDosPath_(s); if (!fullPath) { R = _GetFileName(R); } } } } } else if (!noSlowAPI && !fullPath) //the slow way. Can get only names, not paths. { using (new _AllProcesses(out var p, out int n)) { for (int i = 0; i < n; i++) { if (p[i].processID == processId) { R = p[i].GetName(cannotOpen: true); break; } } } } return(R); //Would be good to cache process names here. But process id can be reused quickly. Use GetNameCached_ instead. // tested: a process id is reused after creating ~100 processes (and waiting until exits). It takes ~2 s. // The window finder is optimized to call this once for each process and not for each window. }
static void _Print(object text, string cp, int cln, string cmn) { string s = AOutput.ObjectToString_(text); string prefix = null; if (s.Starts("<>")) { prefix = "<>"; s = s.Substring(2); } AOutput.Write($"{prefix}Debug: {cmn} ({APath.GetFileName(cp)}:{cln}): {s}"); }
/// <summary> /// Loads XML file in a safer way. /// Uses <see cref="XDocument.Load(XmlReader, LoadOptions)"/> and <see cref="AFile.WaitIfLocked"/>. /// </summary> /// <param name="file">File. Must be full path. Can contain environment variables etc, see <see cref="APath.ExpandEnvVar"/>.</param> /// <param name="options"></param> /// <exception cref="ArgumentException">Not full path.</exception> /// <exception cref="Exception">Exceptions of <see cref="XDocument.Load"/>.</exception> /// <remarks> /// Unlike <see cref="XDocument.Load(string, LoadOptions)"/>, does not replace <c>\r\n</c> with <c>\n</c>. /// </remarks> public static XDocument LoadDoc(string file, LoadOptions options = default) { file = APath.NormalizeForNET_(file); return(AFile.WaitIfLocked(() => { using var r = new XmlTextReader(file); if (0 == (options & LoadOptions.PreserveWhitespace)) { r.WhitespaceHandling = WhitespaceHandling.Significant; } return XDocument.Load(r, options); })); }
/// <summary> /// Loads XML file in a safer way. /// Uses <see cref="XElement.Load(XmlReader, LoadOptions)"/> and <see cref="AFile.WaitIfLocked"/>. /// </summary> /// <param name="file">File. Must be full path. Can contain environment variables etc, see <see cref="APath.ExpandEnvVar"/>.</param> /// <param name="options"></param> /// <exception cref="ArgumentException">Not full path.</exception> /// <exception cref="Exception">Exceptions of <see cref="XElement.Load"/>.</exception> /// <remarks> /// Unlike <see cref="XElement.Load(string, LoadOptions)"/>, does not replace <c>\r\n</c> with <c>\n</c>. /// </remarks> public static XElement LoadElem(string file, LoadOptions options = default) { file = APath.NormalizeForNET_(file); return(AFile.WaitIfLocked(() => { using var r = new XmlTextReader(file); //to preserve \r\n if (0 == (options & LoadOptions.PreserveWhitespace)) { r.WhitespaceHandling = WhitespaceHandling.Significant; //to save correctly formatted. Default of XElement.Load(string). } return XElement.Load(r, options); })); //tested: XElement.Load(string) uses XmlReader.Create. It replaces \r\n with \n and does not have an option to preserve \r\n. }
/// <summary> /// Gets <see cref="FileId"/> of a file or directory. /// Returns false if fails. Supports <see cref="ALastError"/>. /// </summary> /// <param name="path">Full path. Supports environment variables (see <see cref="APath.ExpandEnvVar"/>).</param> /// <param name="fileId"></param> public static unsafe bool GetFileId(string path, out FileId fileId) { path = APath.NormalizeMinimally_(path, false); fileId = new FileId(); using var h = Api.CreateFile(path, Api.FILE_READ_ATTRIBUTES, Api.FILE_SHARE_ALL, default, Api.OPEN_EXISTING, Api.FILE_FLAG_BACKUP_SEMANTICS); if (h.Is0) { return(false); } if (!Api.GetFileInformationByHandle(h, out var k)) { return(false); } fileId.VolumeSerialNumber = (int)k.dwVolumeSerialNumber; fileId.FileIndex = k.FileIndex; return(true); }
//Used by Run and RunConsole. static string _NormalizeFile(bool runConsole, string file, out bool isFullPath, out bool isShellPath) { isShellPath = isFullPath = false; file = APath.ExpandEnvVar(file); if (file.NE()) { throw new ArgumentException(); } if (runConsole || !(isShellPath = APath.IsShellPath_(file))) { if (isFullPath = APath.IsFullPath(file)) { var fl = runConsole ? PNFlags.DontExpandDosPath : PNFlags.DontExpandDosPath | PNFlags.DontPrefixLongPath; file = APath.Normalize_(file, fl, true); //ShellExecuteEx supports long path prefix for exe but not for documents. //Process.Run supports long path prefix, except when the exe is .NET. if (!runConsole) { file = APath.UnprefixLongPath(file); } if (ADisableFsRedirection.IsSystem64PathIn32BitProcess(file) && !AFile.ExistsAsAny(file)) { file = ADisableFsRedirection.GetNonRedirectedSystemPath(file); } } else if (!APath.IsUrl(file)) { //ShellExecuteEx searches everywhere except in app folder. //Process.Run prefers current directory. var s2 = AFile.SearchPath(file); if (s2 != null) { file = s2; isFullPath = true; } } } return(file); }
/// <summary> /// Opens or creates a database file. /// </summary> /// <param name="file"> /// Database file. Can be: /// - Full path. Supports environment variables etc, see <see cref="APath.ExpandEnvVar"/> /// - ":memory:" - create a private, temporary in-memory database. /// - "" - create a private, temporary on-disk database. /// - Starts with "file:" - see sqlite3_open_v2 documentation in SQLite website. /// </param> /// <param name="flags">sqlite3_open_v2 flags, documanted in SQLite website. Default: read-write, create file if does not exist (and parent directory).</param> /// <param name="sql"> /// SQL to execute. For example, one or more ;-separated PRAGMA statements to configure the database connection. Or even "CREATE TABLE IF NOT EXISTS ...". /// This function also always executes "PRAGMA foreign_keys=ON;PRAGMA secure_delete=ON;". /// </param> /// <exception cref="ArgumentException">Not full path.</exception> /// <exception cref="SLException">Failed to open database or execute sql.</exception> /// <remarks> /// Calls sqlite3_open_v2. /// <note>If a variable of this class is used by multiple threads, use <c>lock(variable) { }</c> where need.</note> /// </remarks> public ASqlite(string file, SLFlags flags = SLFlags.ReadWriteCreate, string sql = null) { bool isSpec = file != null && (file.Length == 0 || file == ":memory:" || file.Starts("file:")); if (!isSpec) { file = APath.Normalize(file); if (flags.Has(SLFlags.SQLITE_OPEN_CREATE) && !AFile.ExistsAsFile(file, true)) { AFile.CreateDirectoryFor(file); } } var r = SLApi.sqlite3_open_v2(AConvert.ToUtf8(file), ref _db, flags, null); if (r != 0) { Dispose(); throw new SLException(r, "sqlite3_open " + file); } Execute("PRAGMA foreign_keys=ON;PRAGMA secure_delete=ON;" + sql); }
public static void Dialog(object text, [CallerFilePath] string cp = null, [CallerLineNumber] int cln = 0, [CallerMemberName] string cmn = null) { string s = AOutput.ObjectToString_(text); ADialog.Show("Debug", s, flags: DFlags.ExpandDown, expandedText: $"{cmn} ({APath.GetFileName(cp)}:{cln})"); }
/// <summary> /// Runs/opens a program, document, directory (folder), URL, new email, Control Panel item etc. /// The returned <see cref="RResult"/> variable contains some process info - process id etc. /// </summary> /// <param name="file"> /// Examples: /// - <c>@"C:\file.txt"</c> /// - <c>AFolders.Documents</c> /// - <c>AFolders.System + "notepad.exe"</c> /// - <c>@"%AFolders.System%\notepad.exe"</c> /// - <c>@"%TMP%\file.txt"</c> /// - <c>"notepad.exe"</c> /// - <c>@"..\folder\x.exe"</c> /// - <c>"http://a.b.c/d"</c> /// - <c>"file:///path"</c> /// - <c>"mailto:[email protected]"</c> /// - <c>":: ITEMIDLIST"</c> /// - <c>@"::{CLSID}"</c> /// - <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>. /// </param> /// <param name="args"> /// Command line arguments. /// This function expands environment variables if starts with <c>"%"</c> or <c>"\"%"</c>. /// </param> /// <param name="flags"></param> /// <param name="curDirEtc"> /// Allows to specify more parameters: current directory, verb, etc. /// If string, it sets initial current directory for the new process. If "", gets it from <i>file</i>. More info: <see cref="ROptions.CurrentDirectory"/>. /// </param> /// <exception cref="ArgumentException">Used both ROptions.Verb and RFlags.Admin.</exception> /// <exception cref="AuException">Failed. For example, the file does not exist.</exception> /// <remarks> /// It works like when you double-click a file icon. It may start new process or not. For example it may just activate window if the program is already running. /// Uses API <msdn>ShellExecuteEx</msdn>. /// Similar to <see cref="Process.Start(string, string)"/>. /// /// The <i>file</i> parameter can be: /// - Full path of a file or directory. Examples: <c>@"C:\file.txt"</c>, <c>AFolders.Documents</c>, <c>AFolders.System + "notepad.exe"</c>, <c>@"%AFolders.System%\notepad.exe"</c>. /// - Filename of a file or directory, like <c>"notepad.exe"</c>. The function calls <see cref="AFile.SearchPath"/>. /// - Path relative to <see cref="AFolders.ThisApp"/>. Examples: <c>"x.exe"</c>, <c>@"subfolder\x.exe"</c>, <c>@".\subfolder\x.exe"</c>, <c>@"..\another folder\x.exe"</c>. /// - URL. Examples: <c>"http://a.b.c/d"</c>, <c>"file:///path"</c>. /// - Email, like <c>"mailto:[email protected]"</c>. Subject, body etc also can be specified, and Google knows how. /// - Shell object's ITEMIDLIST like <c>":: ITEMIDLIST"</c>. See <see cref="APidl.ToBase64String"/>, <see cref="AFolders.Virtual"/>. Can be used to open virtual folders and items like Control Panel. /// - Shell object's parsing name, like <c>@"::{CLSID}"</c>. See <see cref="APidl.ToShellString"/>, <see cref="AFolders.VirtualPidl"/>. Can be used to open virtual folders and items like Control Panel. /// - To run a Windows Store App, use <c>@"shell:AppsFolder\WinStoreAppId"</c> format. Examples: <c>@"shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"</c>, <c>@"shell:AppsFolder\windows.immersivecontrolpanel_cw5n1h2txyewy!microsoft.windows.immersivecontrolpanel"</c>. To discover the string use <see cref="AWnd.More.GetWindowsStoreAppId"/> or Google. /// /// Supports environment variables, like <c>@"%TMP%\file.txt"</c>. See <see cref="APath.ExpandEnvVar"/>. /// </remarks> /// <seealso cref="AWnd.FindOrRun"/> /// <example> /// Run notepad and wait for its window. /// <code><![CDATA[ /// AExec.Run("notepad.exe"); /// AWnd w = AWnd.Wait(10, true, "*- Notepad", "Notepad"); /// ]]></code> /// Run notepad or activate its window. /// <code><![CDATA[ /// AWnd w = AWnd.FindOrRun("*- Notepad", run: () => AExec.Run("notepad.exe")); /// ]]></code> /// </example> public static RResult Run(string file, string args = null, RFlags flags = 0, ROptions curDirEtc = null) { //SHOULDDO: from UAC IL admin run as user by default. Add flag UacInherit. Api.SHELLEXECUTEINFO x = default; x.cbSize = Api.SizeOf(x); x.fMask = Api.SEE_MASK_NOZONECHECKS | Api.SEE_MASK_NOASYNC | Api.SEE_MASK_NOCLOSEPROCESS | Api.SEE_MASK_CONNECTNETDRV | Api.SEE_MASK_UNICODE; x.nShow = Api.SW_SHOWNORMAL; bool curDirFromFile = false; var more = curDirEtc; if (more != null) { x.lpVerb = more.Verb; var cd = more.CurrentDirectory; if (cd != null) { if (cd.Length == 0) { curDirFromFile = true; } else { cd = APath.ExpandEnvVar(cd); } } x.lpDirectory = cd; if (!more.OwnerWindow.IsEmpty) { x.hwnd = more.OwnerWindow.Wnd.Window; } switch (more.WindowState) { case ProcessWindowStyle.Hidden: x.nShow = Api.SW_HIDE; break; case ProcessWindowStyle.Minimized: x.nShow = Api.SW_SHOWMINIMIZED; break; case ProcessWindowStyle.Maximized: x.nShow = Api.SW_SHOWMAXIMIZED; break; } } if (flags.Has(RFlags.Admin)) { if (more?.Verb != null && !more.Verb.Eqi("runas")) { throw new ArgumentException("Cannot use Verb with flag Admin"); } x.lpVerb = "runas"; } else if (x.lpVerb != null) { x.fMask |= Api.SEE_MASK_INVOKEIDLIST; //makes slower. But verbs are rarely used. } if (0 == (flags & RFlags.ShowErrorUI)) { x.fMask |= Api.SEE_MASK_FLAG_NO_UI; } if (0 == (flags & RFlags.WaitForExit)) { x.fMask |= Api.SEE_MASK_NO_CONSOLE; } file = _NormalizeFile(false, file, out bool isFullPath, out bool isShellPath); APidl pidl = null; if (isShellPath) //":: Base64ITEMIDLIST" or "::{CLSID}..." (we convert it too because the API does not support many) { pidl = APidl.FromString(file); //does not throw if (pidl != null) { x.lpIDList = pidl.UnsafePtr; x.fMask |= Api.SEE_MASK_INVOKEIDLIST; } else { x.lpFile = file; } } else { x.lpFile = file; if (curDirFromFile && isFullPath) { x.lpDirectory = APath.GetDirectoryPath(file); } } if (!args.NE()) { x.lpParameters = APath.ExpandEnvVar(args); } AWnd.More.EnableActivate(); if (!Api.ShellExecuteEx(ref x)) { throw new AuException(0, $"*run '{file}'"); } pidl?.Dispose(); var R = new RResult(); bool waitForExit = 0 != (flags & RFlags.WaitForExit); bool needHandle = flags.Has(RFlags.NeedProcessHandle); WaitHandle_ ph = null; if (!x.hProcess.Is0) { if (waitForExit || needHandle) { ph = new WaitHandle_(x.hProcess, true); } if (!waitForExit) { R.ProcessId = AProcess.ProcessIdFromHandle(x.hProcess); } } try { Api.AllowSetForegroundWindow(Api.ASFW_ANY); if (x.lpVerb != null && Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { Thread.CurrentThread.Join(50); //need min 5-10 for file Properties. And not Sleep. } if (ph != null) { if (waitForExit) { ph.WaitOne(); if (Api.GetExitCodeProcess(x.hProcess, out var ec)) { R.ProcessExitCode = ec; } } if (needHandle) { R.ProcessHandle = ph; } } } finally { if (R.ProcessHandle == null) { if (ph != null) { ph.Dispose(); } else { x.hProcess.Dispose(); } } } return(R); //tested: works well in MTA thread. //rejected: in QM2, run also has a 'window' parameter. However it just makes limited, unclear etc, and therefore rarely used. Instead use AWnd.FindOrRun or Find/Run/Wait like in the examples. //rejected: in QM2, run also has 'autodelay'. Better don't add such hidden things. Let the script decide what to do. }