mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-06-19 13:50:09 +10:00
1085 lines
36 KiB
C#
1085 lines
36 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using DataUtils;
|
|
using AppxPackage;
|
|
using System.IO;
|
|
using static AppxPackage.PackageManager;
|
|
using Newtonsoft.Json;
|
|
using Win32;
|
|
|
|
namespace PkgCLI
|
|
{
|
|
class Program
|
|
{
|
|
public delegate _I_HResult PackageOperation (string filePath, IEnumerable<string> depUris, DeploymentOptions options, PackageProgressCallback progress = null);
|
|
static readonly string [] helpArgs = new string [] {
|
|
"/?",
|
|
"-?",
|
|
"help",
|
|
"/help",
|
|
"-help",
|
|
"/h",
|
|
"-h"
|
|
};
|
|
static bool IsHelpParam (string arg) => helpArgs.Contains (arg.Normalize ());
|
|
static void PrintVersion ()
|
|
{
|
|
var verFilePath = Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "version");
|
|
var verFileInst = new _I_File (verFilePath);
|
|
var verstr = verFileInst.Content?.Trim () ?? "0.0.0.1";
|
|
Console.WriteLine (
|
|
$"Package Manager CLI [Version {verstr}]\n(C) Windows Modern. All rights reserved."
|
|
);
|
|
}
|
|
static void PrintTotalHelp ()
|
|
{
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli <command> [arguments]
|
|
|
|
Commands:
|
|
/install Install a package.
|
|
/update Update a package (previous version must be installed).
|
|
/register Register an app manifest file.
|
|
/stage Stage a package.
|
|
/remove Remove the package.
|
|
/read Read package information.
|
|
/get List all installed apps.
|
|
/find Find installed apps.
|
|
/active Launch an app.
|
|
/config Configure settings (omit to show current config).
|
|
/version Display version information.
|
|
/encoding Set console output encoding. With other commands.
|
|
/? Show this help.
|
|
");
|
|
}
|
|
public static bool IsFilePathInList (List<string> filelist, string file)
|
|
{
|
|
foreach (var f in filelist)
|
|
{
|
|
if (f.NEquals (file)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
public static void AddFiles (List<string> filelist, string file)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (file)) return;
|
|
if (!File.Exists (file)) file = Path.Combine (Environment.CurrentDirectory, file);
|
|
if (!File.Exists (file)) return;
|
|
var ext = Path.GetExtension (file);
|
|
if (ext.NEquals (".txt"))
|
|
{
|
|
var lines = File.ReadAllLines (file);
|
|
foreach (var l in lines)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (l)) continue;
|
|
var line = l.Trim ();
|
|
if (!File.Exists (l)) line = Path.Combine (Path.GetDirectoryName (file), l);
|
|
if (!File.Exists (line)) continue;
|
|
if (!IsFilePathInList (filelist, line))
|
|
filelist.Add (line);
|
|
}
|
|
}
|
|
else if (!IsFilePathInList (filelist, file)) filelist.Add (file);
|
|
}
|
|
public static void AddNStringItems (List<string> strlist, string str)
|
|
{
|
|
var find = false;
|
|
foreach (var l in strlist)
|
|
if (l.NEquals (str))
|
|
{
|
|
find = true;
|
|
break;
|
|
}
|
|
if (!find) strlist.Add (str);
|
|
}
|
|
public static void ToFormatString (PMPackageInfo pkg, IEnumerable<string> filter)
|
|
{
|
|
if (pkg == null) return;
|
|
var labels = new List<string> ();
|
|
var values = new List<string> ();
|
|
|
|
var id = pkg.Identity;
|
|
labels.Add ("Identity:Name"); values.Add (id.Name);
|
|
labels.Add ("Identity:Publisher"); values.Add (id.Publisher);
|
|
labels.Add ("Identity:FamilyName"); values.Add (id.FamilyName);
|
|
labels.Add ("Identity:FullName"); values.Add (id.FullName);
|
|
labels.Add ("Identity:PublisherId"); values.Add (id.PublisherId);
|
|
labels.Add ("Identity:ResourceId"); values.Add (id.ResourceId);
|
|
labels.Add ("Identity:Version"); values.Add (id.Version?.ToString () ?? "");
|
|
labels.Add ("Identity:ProcessArchitecture"); values.Add (string.Join (", ", id.ProcessArchitecture));
|
|
|
|
var prop = pkg.Properties;
|
|
labels.Add ("Properties:DisplayName"); values.Add (prop.DisplayName);
|
|
labels.Add ("Properties:Description"); values.Add (prop.Description);
|
|
labels.Add ("Properties:Publisher"); values.Add (prop.Publisher);
|
|
labels.Add ("Properties:Framework"); values.Add (prop.Framework.ToString ());
|
|
labels.Add ("Properties:ResourcePackage"); values.Add (prop.ResourcePackage.ToString ());
|
|
labels.Add ("Properties:Logo"); values.Add (prop.Logo);
|
|
|
|
labels.Add ("IsBundle"); values.Add (pkg.IsBundle.ToString ());
|
|
labels.Add ("DevelopmentMode"); values.Add (pkg.DevelopmentMode.ToString ());
|
|
labels.Add ("InstallLocation"); values.Add (pkg.InstallLocation);
|
|
labels.Add ("Users"); values.Add (string.Join ("; ", pkg.Users));
|
|
|
|
Console.WriteLine ($"[{pkg.Identity.FullName}]");
|
|
var indicesToOutput = new List<int> ();
|
|
bool outputAll = (filter == null || !filter.Any ());
|
|
|
|
for (int i = 0; i < labels.Count; i++)
|
|
{
|
|
if (outputAll)
|
|
{
|
|
indicesToOutput.Add (i);
|
|
}
|
|
else
|
|
{
|
|
string label = labels [i];
|
|
foreach (string patternRaw in filter)
|
|
{
|
|
if (MatchPattern (label, patternRaw))
|
|
{
|
|
indicesToOutput.Add (i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (indicesToOutput.Count == 0) return;
|
|
int maxLabelLen = 0;
|
|
foreach (int i in indicesToOutput)
|
|
if (labels [i].Length > maxLabelLen) maxLabelLen = labels [i].Length;
|
|
string format = "{0,-" + maxLabelLen + "} = {1}";
|
|
foreach (int i in indicesToOutput)
|
|
Console.WriteLine (format, labels [i], values [i]);
|
|
}
|
|
/// <summary>
|
|
/// 匹配模式(忽略大小写,首尾空格,支持通配符 *)
|
|
/// </summary>
|
|
private static bool MatchPattern (string text, string pattern)
|
|
{
|
|
if (string.IsNullOrEmpty (pattern)) return true;
|
|
pattern = pattern.Trim ();
|
|
text = text ?? "";
|
|
|
|
if (pattern == "*") return true;
|
|
// 忽略大小写
|
|
var comparison = StringComparison.OrdinalIgnoreCase;
|
|
|
|
// 检查通配符位置
|
|
bool startsWithStar = pattern.StartsWith ("*");
|
|
bool endsWithStar = pattern.EndsWith ("*");
|
|
|
|
if (startsWithStar && endsWithStar)
|
|
{
|
|
// *middle* 包含子串
|
|
string middle = pattern.Substring (1, pattern.Length - 2);
|
|
return text.IndexOf (middle, comparison) >= 0;
|
|
}
|
|
else if (startsWithStar)
|
|
{
|
|
// *suffix 以 suffix 结尾
|
|
string suffix = pattern.Substring (1);
|
|
return text.EndsWith (suffix, comparison);
|
|
}
|
|
else if (endsWithStar)
|
|
{
|
|
// prefix* 以 prefix 开头
|
|
string prefix = pattern.Substring (0, pattern.Length - 1);
|
|
return text.StartsWith (prefix, comparison);
|
|
}
|
|
else
|
|
{
|
|
// 精确匹配(忽略大小写)
|
|
return string.Equals (text, pattern, comparison);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// 判断字符串是否为有效的包全名 (Package Full Name)
|
|
/// 格式: <IdentityName>_<Version>_<ProcessorArchitecture>_<ResourceId>_<PublisherId>
|
|
/// 其中 ResourceId 可以为空(表现为连续两个下划线)
|
|
/// </summary>
|
|
public static bool IsPackageFullName (string fullName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (fullName)) return false;
|
|
string [] parts = fullName.Split (new char [] { '_' }, StringSplitOptions.None);
|
|
if (parts.Length != 5)
|
|
return false;
|
|
if (string.IsNullOrEmpty (parts [0])) // IdentityName
|
|
return false;
|
|
if (string.IsNullOrEmpty (parts [1])) // Version
|
|
return false;
|
|
if (string.IsNullOrEmpty (parts [2])) // ProcessorArchitecture
|
|
return false;
|
|
if (string.IsNullOrEmpty (parts [4])) // PublisherId
|
|
return false;
|
|
if (!parts [1].Contains ('.')) return false;
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// 判断字符串是否为有效的包系列名 (Package Family Name)
|
|
/// 格式: <IdentityName>_<PublisherId>
|
|
/// </summary>
|
|
public static bool IsPackageFamilyName (string familyName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (familyName)) return false;
|
|
string [] parts = familyName.Split ('_');
|
|
if (parts.Length != 2) return false;
|
|
if (string.IsNullOrEmpty (parts [0]) || string.IsNullOrEmpty (parts [1]))
|
|
return false;
|
|
return true;
|
|
}
|
|
/// <summary>
|
|
/// 从 args[startIndex..] 生成安全的命令行字符串
|
|
/// </summary>
|
|
private static string BuildCommandLine (string [] args, int startIndex)
|
|
{
|
|
if (args.Length <= startIndex) return null;
|
|
var sb = new StringBuilder ();
|
|
for (int i = startIndex; i < args.Length; i++)
|
|
{
|
|
if (i > startIndex) sb.Append (' ');
|
|
sb.Append (EscapeArgument (args [i]));
|
|
}
|
|
return sb.ToString ();
|
|
}
|
|
/// <summary>
|
|
/// 按 Win32 命令行规则转义单个参数
|
|
/// </summary>
|
|
private static string EscapeArgument (string arg)
|
|
{
|
|
if (string.IsNullOrEmpty (arg)) return "\"\"";
|
|
bool needQuotes = false;
|
|
foreach (char c in arg)
|
|
{
|
|
if (char.IsWhiteSpace (c) || c == '"')
|
|
{
|
|
needQuotes = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!needQuotes) return arg;
|
|
var sb = new StringBuilder ();
|
|
sb.Append ('"');
|
|
foreach (char c in arg)
|
|
{
|
|
if (c == '"') sb.Append ("\\\"");
|
|
else sb.Append (c);
|
|
}
|
|
sb.Append ('"');
|
|
return sb.ToString ();
|
|
}
|
|
public static readonly string [] configItems = new string [] {
|
|
"AppMetadataItems"
|
|
};
|
|
public static void RefreshConfig ()
|
|
{
|
|
var conf = new InitConfig (Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "config.ini"));
|
|
var sSettings = conf.GetSection ("Settings");
|
|
var kAppMetadatas = sSettings.GetKey ("PkgCLI:AppMetadataItems");
|
|
var appMd = kAppMetadatas.ReadString ("Id,BackgroundColor,DisplayName,ForegroundText,ShortName,SmallLogo,Square44x44Logo");
|
|
var appMdList = (appMd ?? "").Split (',', ';', '|');
|
|
for (var i = 0; i < appMdList.Length; i ++)
|
|
{
|
|
appMdList [i] = appMdList [i].Trim ();
|
|
}
|
|
PackageReader.UpdateApplicationItems (appMdList);
|
|
}
|
|
static void Main (string [] args)
|
|
{
|
|
var currencoding = Console.OutputEncoding;
|
|
try
|
|
{
|
|
//Console.OutputEncoding = Encoding.UTF8;
|
|
if (args.Length <= 0 || args.Length >= 1 && IsHelpParam (args [0]))
|
|
{
|
|
PrintVersion ();
|
|
PrintTotalHelp ();
|
|
return;
|
|
}
|
|
var parser = new CliParsing ();
|
|
parser.Params = new HashSet<CmdParamName> (new CmdParamName [] {
|
|
new CmdParamName ("install", new string [] { "add" }),
|
|
new CmdParamName ("register", new string [] { "reg" }),
|
|
new CmdParamName ("update", new string [] { "up" }),
|
|
new CmdParamName ("stage"),
|
|
new CmdParamName ("remove", new string [] { "uninstall" }),
|
|
new CmdParamName ("read"),
|
|
new CmdParamName ("get"),
|
|
new CmdParamName ("find"),
|
|
new CmdParamName ("active", new string [] { "launch", "start", "activate" }),
|
|
new CmdParamName ("config", new string [] { "conf" }),
|
|
new CmdParamName ("version", new string [] { "ver" }),
|
|
new CmdParamName ("help", new string [] { "?", "h" }),
|
|
new CmdParamName ("developmode", new string [] { "develop" }),
|
|
new CmdParamName ("forceappshutdown", new string [] { "forceshutdown" , "appshutdown", "forcesd", "force"}),
|
|
new CmdParamName ("installallresources", new string [] {"allresources", "allres"}),
|
|
new CmdParamName ("savexml"),
|
|
new CmdParamName ("savejson"),
|
|
new CmdParamName ("fullname"),
|
|
new CmdParamName ("filter"),
|
|
new CmdParamName ("package", new string [] { "pkg" }),
|
|
new CmdParamName ("manifest", new string [] { "mani" }),
|
|
new CmdParamName ("usepri", new string [] { "pri" }),
|
|
new CmdParamName ("item"),
|
|
new CmdParamName ("set"),
|
|
new CmdParamName ("refresh"),
|
|
new CmdParamName ("show"),
|
|
new CmdParamName ("encoding", new string [] { "en", "charset", "encode" }),
|
|
new CmdParamName ("yes", new string [] {"y", "agree"})
|
|
});
|
|
RefreshConfig ();
|
|
var cmds = parser.Parse (args);
|
|
if (cmds.ParamContains ("encoding"))
|
|
{
|
|
#region help text: encoding
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /encoding:<code_page|name>
|
|
pkgcli /en:<code_page|name>
|
|
|
|
Operation:
|
|
Set the console output encoding for the current session.
|
|
Useful when the output contains non-ASCII characters.
|
|
|
|
Arguments:
|
|
<code_page> Numeric code page identifier (e.g., 65001 for UTF-8, 936 for GB2312).
|
|
<name> Encoding name (e.g., utf-8, gb2312, windows-1252).
|
|
|
|
Examples:
|
|
pkgcli /encoding:65001 (Set to UTF-8)
|
|
pkgcli /encoding:utf-8
|
|
pkgcli /en:936 (Set to GB2312/GBK)
|
|
pkgcli /en:gb2312
|
|
|
|
Note:
|
|
This setting only affects the current pkgcli process and does not persist.
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
try
|
|
{
|
|
var c = cmds.GetFromId ("encoding");
|
|
var i = 0;
|
|
var isint = false;
|
|
Encoding en = null;
|
|
try { i = Convert.ToInt32 (c.Value); isint = true; } catch { isint = false; }
|
|
if (isint) en = Encoding.GetEncoding (i);
|
|
else en = Encoding.GetEncoding (c.Value);
|
|
Console.OutputEncoding = en;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
Console.WriteLine ($"Warning: Set encoding for output failed. Exception {ex.GetType ()}\nMessage: \n {ex.Message}\nWe will use default encoding.");
|
|
Console.ResetColor ();
|
|
}
|
|
}
|
|
if (CliPasingUtils.ParamsContainsOr (cmds, "install", "register", "update", "stage"))
|
|
{
|
|
#region help text: install register, update, stage
|
|
PrintVersion ();
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli <command> <file_path...> [/developmode] [/force] [/allres]
|
|
|
|
Commands:
|
|
/install Install a package. Supports .appx, .appxbundle, .msix, .msixbundle.
|
|
/register Register a package. Supports AppxManifest.xml or *.appxmanifest.
|
|
/update Update an installed app. Supports same formats as /install.
|
|
/stage Stage a package (pre-deploy). Supports same formats as /install.
|
|
|
|
Options (DeploymentOptions flags):
|
|
/developmode Install/register in development mode. Do not use with bundle packages.
|
|
/force Force application shutdown to allow registration when the package or its dependencies are in use.
|
|
/allres Skip resource applicability checks; stage/register all resource packages in a bundle.
|
|
|
|
Arguments:
|
|
<file_path...> One or more package or manifest file paths. Can be:
|
|
- Direct path to .appx, .msix, .appxbundle, .msixbundle, or .xml manifest.
|
|
- A .txt file containing a list of such paths (one per line).
|
|
- If no command is repeated, the command applies to all listed files.
|
|
|
|
Examples:
|
|
pkgcli /install MyApp.appx
|
|
pkgcli /register /manifest:AppxManifest.xml /developmode
|
|
pkgcli /update MyApp.msixbundle /force
|
|
pkgcli /stage MyApp.appx /allres
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var options = DeploymentOptions.None;
|
|
if (CliPasingUtils.ParamContains (cmds, "developmode")) options |= DeploymentOptions.DevelopmentMode;
|
|
if (CliPasingUtils.ParamContains (cmds, "forceappshutdown")) options |= DeploymentOptions.ForceAppShutdown;
|
|
if (CliPasingUtils.ParamContains (cmds, "installallresources")) options |= DeploymentOptions.InstallAllResources;
|
|
var totallist = new List<Tuple<int, string>> ();
|
|
var filelist = new List<string> ();
|
|
foreach (var f in cmds)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (f.Id))
|
|
{
|
|
var file = f.Value;
|
|
AddFiles (filelist, file);
|
|
}
|
|
else
|
|
{
|
|
switch (f.Id)
|
|
{
|
|
case "install":
|
|
case "register":
|
|
case "update":
|
|
case "stage":
|
|
AddFiles (filelist, f.Value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
foreach (var f in filelist) totallist.Add (new Tuple<int, string> (0, f));
|
|
if (cmds.ParamContains ("register") && cmds.ParamContains ("fullname"))
|
|
{
|
|
foreach (var f in cmds)
|
|
{
|
|
if (f.Id.NEquals ("fullname"))
|
|
{
|
|
if (string.IsNullOrWhiteSpace (f.Value))
|
|
{
|
|
totallist.Add (new Tuple<int, string> (1, f.Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
PackageOperation ope = null;
|
|
if (cmds.ParamContains ("install")) ope = AddPackage;
|
|
else if (cmds.ParamContains ("register")) ope = RegisterPackage;
|
|
else if (cmds.ParamContains ("update")) ope = UpdatePackage;
|
|
else if (cmds.ParamContains ("stage")) ope = StagePackage;
|
|
for (int i = 0; i < totallist.Count; i++)
|
|
{
|
|
Console.WriteLine ();
|
|
var file = totallist [i];
|
|
Console.Write ($"\r({i + 1}/{totallist.Count}) Operation in progress...");
|
|
var hr = new _I_HResult (0);
|
|
var tempope = ope;
|
|
if (file.Item1 == 1)
|
|
{
|
|
if (cmds.ParamContains ("register")) tempope = RegisterPackageByFullName;
|
|
}
|
|
hr = tempope (file.Item2, null, options, prog => {
|
|
var str = $"\r({i + 1}/{filelist.Count}) Operation in progress... {prog}% of {(int)((i + prog * 0.01) / filelist.Count * 100)}%";
|
|
Console.Write (str);
|
|
});
|
|
if (hr.Failed)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.Write ($"\nPackage {i + 1} \"{file.Item2}\" Operation Error ({"0x" + hr.HResult.ToString ("X8")}): \n{hr.Message}");
|
|
Console.ResetColor ();
|
|
}
|
|
}
|
|
Console.WriteLine ("\nAll Done.");
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("remove"))
|
|
{
|
|
#region help text: remove
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /remove <package_full_name> [<package_full_name>...] [/yes]
|
|
|
|
Operation:
|
|
Remove one or more packages.
|
|
|
|
Arguments:
|
|
<package_full_name> The full name of the installed package.
|
|
Format: <identity_name>_<version>_<architecture>_<resource_id>_<publisher_id>
|
|
Example: Microsoft.WinJS.1.0_1.0.9200.20789_neutral__8wekyb3d8bbwe
|
|
|
|
Options:
|
|
/yes Automatically confirm the uninstallation without prompting.
|
|
Aliases: /y, /agree
|
|
|
|
You can specify multiple full names separated by spaces.
|
|
|
|
Examples:
|
|
pkgcli /remove MyPackage_1.0.0.0_x64__abcd1234
|
|
pkgcli /remove PackageA_1.0.0.0_neutral__abcd1234 PackageB_2.0.0.0_x86__efgh5678 /yes
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var list = new List<string> ();
|
|
foreach (var c in cmds)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (c.Id) || c.Id.NEquals ("remove"))
|
|
{
|
|
if (!string.IsNullOrWhiteSpace (c.Value)) list.Add (c.Value);
|
|
}
|
|
}
|
|
var agreecmd = cmds.GetFromId ("yes");
|
|
bool? agree = null;
|
|
if (agreecmd != null) agree = true;
|
|
if (agree == null)
|
|
{
|
|
if (list.Count <= 0) agree = true;
|
|
else
|
|
{
|
|
Console.Write ($"We will uninstall {list.Count} app(-s). Do you want to continue?(Y/N) ");
|
|
var userinput = Console.ReadLine ();
|
|
if (userinput.NEquals ("y") || userinput.NEquals ("yes")) agree = true;
|
|
else agree = false;
|
|
}
|
|
}
|
|
if (agree == false) throw new OperationCanceledException ("User canceled.");
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
Console.WriteLine ();
|
|
var file = list [i];
|
|
Console.Write ($"\r({i + 1}/{list.Count}) Operation in progress...");
|
|
var hr = RemovePackage (file, prog => {
|
|
var str = $"\r({i + 1}/{list.Count}) Operation in progress... {prog}% of {(int)((i + prog * 0.01) / list.Count * 100)}%";
|
|
Console.Write (str);
|
|
});
|
|
if (hr.Failed)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.Write ($"\nPackage {i + 1} \"{file}\" Operation Error ({"0x" + hr.HResult.ToString ("X8")}): \n{hr.Message}");
|
|
Console.ResetColor ();
|
|
}
|
|
}
|
|
Console.WriteLine ("\nAll Done.");
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("get"))
|
|
{
|
|
#region help text: get
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /get [/filter:<filter1,filter2,...>]
|
|
|
|
Operation:
|
|
List all installed packages with their properties.
|
|
|
|
Options:
|
|
/filter:<filters> Show only the specified properties.
|
|
Filters are case-insensitive and support '*' wildcard.
|
|
Multiple filters separated by comma ',' or semicolon ';'.
|
|
Examples:
|
|
/filter:Identity:Name,Identity:Version
|
|
/filter:Identity:* (all Identity properties)
|
|
/filter:Properties:DisplayName
|
|
|
|
Output:
|
|
Each package is printed as a section [FullName] followed by key = value lines.
|
|
Redirect output to a file to save as INI-like format.
|
|
|
|
Examples:
|
|
pkgcli /get
|
|
pkgcli /get /filter:Identity:FullName,Properties:DisplayName
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var filter = new HashSet<string> (new NormalizeStringComparer ());
|
|
if (cmds.ParamContains ("filter"))
|
|
{
|
|
foreach (var c in cmds)
|
|
{
|
|
if (c.Id.NEmpty () || c.Id.NEquals ("filter"))
|
|
{
|
|
var items = c.Value.Split (',', ';');
|
|
foreach (var i in items) filter.Add (i);
|
|
}
|
|
}
|
|
}
|
|
var hr = GetPackages ();
|
|
if (hr.Item1.Failed)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.WriteLine ($"Operation Error ({"0x" + hr.Item1.HResult.ToString ("X8")}): \n{hr.Item1.Message}");
|
|
Console.ResetColor ();
|
|
return;
|
|
}
|
|
foreach (var i in hr.Item2)
|
|
{
|
|
ToFormatString (i, filter);
|
|
Console.WriteLine ();
|
|
}
|
|
Console.WriteLine ("[Statistic]");
|
|
Console.WriteLine ($"Length = {hr.Item2.Count}");
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("find"))
|
|
{
|
|
#region help text: find
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine(@"
|
|
Usage:
|
|
pkgcli /find <package_full_name|package_family_name> [/filter:<filters>]
|
|
pkgcli /find <identity_name> <identity_publisher> [/filter:<filters>]
|
|
|
|
Operation:
|
|
Find installed packages matching the given identifier.
|
|
|
|
Arguments:
|
|
<package_full_name> Full name of a package.
|
|
<package_family_name> Family name of a package.
|
|
<identity_name> Name part of the package identity (e.g., ""Microsoft.WindowsStore"").
|
|
<identity_publisher> Publisher part (e.g., ""CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"").
|
|
|
|
Options:
|
|
/filter:<filters> Same as in /get command.
|
|
|
|
Examples:
|
|
pkgcli /find Microsoft.WindowsStore_8wekyb3d8bbwe!App
|
|
pkgcli /find Microsoft.WindowsStore
|
|
pkgcli /find Microsoft.WindowsStore ""CN=Microsoft Corporation, ...""
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var names = new List<string> ();
|
|
foreach (var c in cmds)
|
|
{
|
|
if (c.Id.NEmpty ())
|
|
{
|
|
if (c.Value.NEmpty ()) continue;
|
|
names.Add (c.Value);
|
|
}
|
|
}
|
|
var filter = new HashSet<string> (new NormalizeStringComparer ());
|
|
if (cmds.ParamContains ("filter"))
|
|
{
|
|
foreach (var c in cmds)
|
|
{
|
|
if (c.Id.NEquals ("filter"))
|
|
{
|
|
var items = c.Value.Split (',', ';');
|
|
foreach (var i in items) filter.Add (i);
|
|
}
|
|
}
|
|
}
|
|
var result = new List<Tuple<string, Tuple<_I_HResult, List<PMPackageInfo>>>> ();
|
|
if (names.Count == 2)
|
|
{
|
|
result.Add (new Tuple<string, Tuple<_I_HResult, List<PMPackageInfo>>> (names [0], FindPackage (names [0], names [1])));
|
|
}
|
|
else
|
|
{
|
|
foreach (var n in names)
|
|
{
|
|
if (IsPackageFullName (n))
|
|
{
|
|
result.Add (new Tuple<string, Tuple<_I_HResult, List<PMPackageInfo>>> (n, FindPackageByFullName (n)));
|
|
}
|
|
else
|
|
{
|
|
result.Add (new Tuple<string, Tuple<_I_HResult, List<PMPackageInfo>>> (n, FindPackage (n)));
|
|
}
|
|
}
|
|
}
|
|
foreach (var r in result)
|
|
{
|
|
foreach (var l in r.Item2.Item2)
|
|
{
|
|
ToFormatString (l, filter);
|
|
Console.WriteLine ();
|
|
}
|
|
Console.WriteLine ($"[Statistic:{r.Item1}]");
|
|
Console.WriteLine ($"HResult = {"0x" + r.Item2.Item1.HResult.ToString ("X8")}");
|
|
Console.WriteLine ($"Message = {Escape.ToEscape (r.Item2.Item1.Message)}");
|
|
Console.WriteLine ($"Length = {r.Item2.Item2.Count}");
|
|
}
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("active"))
|
|
{
|
|
#region help text: active
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /active <app_user_model_id> [arguments]
|
|
|
|
Operation:
|
|
Launch (activate) a Universal Windows Platform (UWP) app.
|
|
|
|
Arguments:
|
|
<app_user_model_id> The Application User Model ID (AUMID).
|
|
Format: <package_family_name>!<app_id>
|
|
Example: Microsoft.WindowsStore_8wekyb3d8bbwe!App
|
|
|
|
[arguments] Optional command-line arguments passed to the app.
|
|
|
|
Examples:
|
|
pkgcli /active Microsoft.WindowsStore_8wekyb3d8bbwe!App
|
|
pkgcli /active MyAppFamily!App --fullscreen --debug
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
foreach (var c in cmds)
|
|
{
|
|
if (string.IsNullOrWhiteSpace (c.Id) || c.Id.NEquals ("active"))
|
|
{
|
|
if (c.Value.NEmpty ()) continue;
|
|
var i = 0;
|
|
for (i = 0; i < args.Length; i++)
|
|
{
|
|
if (args [i].NNormalize ().IndexOf (c.Value.NNormalize ()) >= 0) break;
|
|
}
|
|
var hr = ActiveApp (c.Value, BuildCommandLine (args, i + 1));
|
|
if (hr.Succeeded) Console.WriteLine ("Done.");
|
|
else
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.Write ($"Operation Error ({"0x" + hr.HResult.ToString ("X8")}): \n{hr.Message}");
|
|
Console.ResetColor ();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("read"))
|
|
{
|
|
#region help text: read
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /read [options] [<file>]
|
|
|
|
Description:
|
|
Read an Appx/MSIX package (.appx, .appxbundle, .msix, .msixbundle) or its manifest file (.xml, .appxpackage, .msixpackage).
|
|
Extracts package/manifest information and outputs as JSON to the console, or saves as a ZIP archive containing either info.json or info.xml.
|
|
|
|
Options:
|
|
/manifest:<file> Specify a manifest file to read.
|
|
/package:<file> Specify a package file to read.
|
|
<file> If no /manifest or /package is provided, the first unnamed parameter is used as the input file.
|
|
File type is auto-detected by extension:
|
|
.xml, .appxpackage, .msixpackage → ManifestReader
|
|
.appx, .appxbundle, .msix, .msixbundle → PackageReader
|
|
|
|
/item:<path> Instead of outputting the whole JSON object, output only the value at the given path.
|
|
Path syntax: use '.' or ':' as separators, case-insensitive.
|
|
Supports indexers (e.g., [0]), .length for collections, and automatic Base64 access (e.g., LogoBase64).
|
|
Examples:
|
|
/item:Identity.Name
|
|
/item:Properties.Publisher
|
|
/item:applications[0]
|
|
/item:applications.length
|
|
/item:applications[0].LogoBase64
|
|
|
|
/usepri Enable PRI resource resolution (localization and scaled images). Required for proper resource string and image resolution.
|
|
|
|
/savexml:<output> Save extracted data as an XML file wrapped in a ZIP archive.
|
|
The archive will contain an info.xml file with the structured data.
|
|
|
|
/savejson:<output> Save extracted data as a JSON file wrapped in a ZIP archive.
|
|
The archive will contain an info.json file with the structured data.
|
|
|
|
/help Display this help text.
|
|
|
|
Output Behavior:
|
|
- If neither /savexml nor /savejson is given, the information is printed directly to the console as JSON.
|
|
- If /item is used, only the value at the specified path is printed (as a string, version, or formatted JSON).
|
|
- If /savexml or /savejson is used, the data is saved to the specified file (ZIP archive) and a success/failure message is shown.
|
|
|
|
Examples:
|
|
pkgcli /read MyApp.appx
|
|
pkgcli /read /manifest:AppxManifest.xml /usepri
|
|
pkgcli /read /package:MyApp.msixbundle /item:Identity.Name
|
|
pkgcli /read /package:MyApp.appx /savejson:output.zip
|
|
pkgcli /read MyApp.appx /item:applications[0].DisplayName
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var filename = "";
|
|
var readtype = "";
|
|
var savename = "";
|
|
var savetype = "default";
|
|
CommandParam cmd = null;
|
|
cmd = cmds.GetFromId ("manifest");
|
|
if (cmd != null)
|
|
{
|
|
filename = cmd.Value;
|
|
readtype = "manifest";
|
|
}
|
|
cmd = cmds.GetFromId ("package");
|
|
if (cmd != null)
|
|
{
|
|
filename = cmd.Value;
|
|
readtype = "package";
|
|
}
|
|
if (string.IsNullOrWhiteSpace (filename) || !File.Exists (filename))
|
|
{
|
|
cmd = cmds.GetFromId ("");
|
|
if (cmd != null)
|
|
{
|
|
if (File.Exists (cmd.Value))
|
|
{
|
|
filename = cmd.Value;
|
|
if (string.IsNullOrWhiteSpace (readtype))
|
|
{
|
|
var ext = Path.GetExtension (cmd.Value);
|
|
if (ext.NEquals (".xml") || ext.NEquals (".appxpackage") || ext.NEquals (".msixpackage"))
|
|
readtype = "manifest";
|
|
else readtype = "package";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (string.IsNullOrWhiteSpace (filename) || !File.Exists (filename))
|
|
{
|
|
cmd = cmds.GetFromId ("read");
|
|
if (cmd != null)
|
|
{
|
|
if (File.Exists (cmd.Value))
|
|
{
|
|
filename = cmd.Value;
|
|
if (string.IsNullOrWhiteSpace (readtype))
|
|
{
|
|
var ext = Path.GetExtension (cmd.Value);
|
|
if (ext.NEquals (".xml") || ext.NEquals (".appxpackage") || ext.NEquals (".msixpackage"))
|
|
readtype = "manifest";
|
|
else readtype = "package";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (string.IsNullOrWhiteSpace (filename) || !File.Exists (filename)) throw new FileNotFoundException ();
|
|
cmd = cmds.GetFromId ("savexml");
|
|
if (cmd != null)
|
|
{
|
|
savename = cmd.Value;
|
|
savetype = "xml";
|
|
}
|
|
if (string.IsNullOrWhiteSpace (savename))
|
|
{
|
|
cmd = cmds.GetFromId ("savejson");
|
|
if (cmd != null)
|
|
{
|
|
savename = cmd.Value;
|
|
savetype = "json";
|
|
}
|
|
}
|
|
if (string.IsNullOrWhiteSpace (savename))
|
|
{
|
|
savetype = "ini";
|
|
}
|
|
switch (readtype)
|
|
{
|
|
default:
|
|
case "package":
|
|
{
|
|
var pr = new PackageReader (filename);
|
|
pr.UsePri = cmds.ParamContains ("usepri");
|
|
pr.EnablePri = true;
|
|
switch (savetype)
|
|
{
|
|
default:
|
|
case "default":
|
|
{
|
|
if (cmds.ParamContains ("item"))
|
|
{
|
|
object value = pr.GetItem (cmds.GetFromId ("item").Value);
|
|
if (value is string) Console.WriteLine (value as string);
|
|
else if (value is DataUtils.Version) Console.WriteLine (value.ToString ());
|
|
else Console.WriteLine (JsonConvert.SerializeObject (value, Formatting.Indented));
|
|
}
|
|
else Console.WriteLine (JsonConvert.SerializeObject (pr.GetJsonObjectForCli (), Formatting.Indented));
|
|
}
|
|
break;
|
|
case "json":
|
|
{
|
|
var res = pr.SaveJsonFileCS (savename);
|
|
if (res) Console.WriteLine ("Succeeded!");
|
|
else Console.WriteLine ("Failed.");
|
|
}
|
|
break;
|
|
case "xml":
|
|
{
|
|
var res = pr.SaveXmlFileCS (savename);
|
|
if (res) Console.WriteLine ("Succeeded!");
|
|
else Console.WriteLine ("Failed.");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "manifest":
|
|
{
|
|
var mr = new ManifestReader (filename);
|
|
mr.UsePri = cmds.ParamContains ("usepri");
|
|
mr.EnablePri = true;
|
|
if (cmds.ParamContains ("item"))
|
|
{
|
|
object value = mr.GetItem (cmds.GetFromId ("item").Value);
|
|
if (value is string) Console.WriteLine (value as string);
|
|
else if (value is DataUtils.Version) Console.WriteLine (value.ToString ());
|
|
else Console.WriteLine (JsonConvert.SerializeObject (value, Formatting.Indented));
|
|
}
|
|
else Console.WriteLine (JsonConvert.SerializeObject (mr.GetJsonObjectForCli (), Formatting.Indented));
|
|
//Console.WriteLine (mr.BuildJsonText ());
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("config"))
|
|
{
|
|
#region help text: config
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /config Show current configuration (all keys).
|
|
pkgcli /config /show:<key> Show value of a specific configuration key.
|
|
pkgcli /config /set:<key> <value> Set configuration key to the given value.
|
|
pkgcli /config /refresh Reload configuration from config.ini.
|
|
|
|
Configuration keys:
|
|
AppMetadataItems Comma-separated list of application metadata fields to read from manifests.
|
|
Default: ""Id,BackgroundColor,DisplayName,ForegroundText,ShortName,SmallLogo,Square44x44Logo""
|
|
|
|
Configuration file:
|
|
config.ini in the same directory as pkgcli.exe.
|
|
|
|
Examples:
|
|
pkgcli /config
|
|
pkgcli /config /show:AppMetadataItems
|
|
pkgcli /config /set:AppMetadataItems ""Id,DisplayName,Logo""
|
|
pkgcli /config /refresh
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
var conf = new InitConfig (Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "config.ini"));
|
|
var sSettings = conf.GetSection ("Settings");
|
|
if (cmds.ParamContains ("refresh")) RefreshConfig ();
|
|
else if (cmds.ParamContains ("set"))
|
|
{
|
|
var cmd = cmds.GetFromId ("set");
|
|
var key = cmd.Value;
|
|
if (string.IsNullOrWhiteSpace (key)) throw new InvalidOperationException ($"key is empty");
|
|
var isfind = false;
|
|
foreach (var i in configItems)
|
|
{
|
|
if (i.NEquals (key))
|
|
{
|
|
isfind = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isfind) throw new KeyNotFoundException ($"Error: cannot find the key \"{key}\"");
|
|
var valuelist = new List<string> ();
|
|
foreach (var c in cmds)
|
|
{
|
|
if (c.Id.NEmpty ())
|
|
{
|
|
if (string.IsNullOrWhiteSpace (c.Value)) continue;
|
|
valuelist.Add (c.Value);
|
|
}
|
|
}
|
|
var res = sSettings.GetKey ($"PkgCLI:{key.Trim ()}").Set (valuelist.Join (","));
|
|
if (res) Console.WriteLine ("Succeeded!");
|
|
else Console.WriteLine ("Failed.");
|
|
}
|
|
else if (cmds.ParamContains ("show"))
|
|
{
|
|
var cmd = cmds.GetFromId ("show");
|
|
var key = cmd.Value;
|
|
if (string.IsNullOrWhiteSpace (key))
|
|
{
|
|
var dict = new Dictionary<string, string> ();
|
|
foreach (var k in configItems)
|
|
{
|
|
var cKey = sSettings.GetKey ($"PkgCLI:{k}");
|
|
dict [k] = cKey.ReadString ("(use default)");
|
|
}
|
|
Console.WriteLine (dict.FormatDictionaryAligned ("="));
|
|
return;
|
|
}
|
|
var isfind = false;
|
|
foreach (var i in configItems)
|
|
{
|
|
if (i.NEquals (key))
|
|
{
|
|
isfind = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isfind) throw new KeyNotFoundException ($"Error: cannot find the key \"{key}\"");
|
|
var value = sSettings.GetKey ($"PkgCLI:{key.Trim ()}").ReadString ("(use default)");
|
|
Console.WriteLine (value);
|
|
}
|
|
return;
|
|
}
|
|
else if (cmds.ParamsContainsOr ("version"))
|
|
{
|
|
#region help text: version
|
|
if (CliPasingUtils.ParamContains (cmds, "help"))
|
|
{
|
|
PrintVersion ();
|
|
Console.WriteLine (@"
|
|
Usage:
|
|
pkgcli /version
|
|
pkgcli /ver
|
|
|
|
Operation:
|
|
Display the current version of Package Manager CLI.
|
|
|
|
Examples:
|
|
pkgcli /version
|
|
pkgcli /ver
|
|
");
|
|
return;
|
|
}
|
|
#endregion
|
|
PrintVersion ();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.WriteLine ("Invalid args. Please use \"/help\" to get help.");
|
|
Console.ResetColor ();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Red;
|
|
Console.WriteLine ($"Exception {ex.GetType ()}, \nMessage: \n {ex.Message}\nStack: \n {ex.StackTrace}");
|
|
}
|
|
finally
|
|
{
|
|
Console.ResetColor ();
|
|
Console.OutputEncoding = currencoding;
|
|
}
|
|
}
|
|
}
|
|
}
|