mirror of
https://github.com/modernw/App-Installer-For-Windows-8.x-Reset.git
synced 2026-04-11 17:57:19 +10:00
Fix bugs.
Update PkgCLI.
This commit is contained in:
@@ -348,8 +348,18 @@ namespace AppxPackage
|
||||
{
|
||||
get
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value))
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind)
|
||||
{
|
||||
value = string.Empty;
|
||||
base [key] = value;
|
||||
@@ -370,8 +380,18 @@ namespace AppxPackage
|
||||
}
|
||||
public string At (string key)
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value)) throw new KeyNotFoundException ($"PRBaseApplication.At: key \"{key}\" not found");
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind) throw new KeyNotFoundException ($"MRApplication: cannot find key \"{key}\"");
|
||||
if (!EnablePri ()) return value;
|
||||
if (PriFileHelper.IsMsResourcePrefix (value))
|
||||
{
|
||||
@@ -383,8 +403,18 @@ namespace AppxPackage
|
||||
}
|
||||
public string NewAt (string key, bool toPriString)
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value))
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind)
|
||||
{
|
||||
value = string.Empty;
|
||||
base [key] = value;
|
||||
|
||||
@@ -741,8 +741,18 @@ namespace AppxPackage
|
||||
{
|
||||
get
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value))
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind)
|
||||
{
|
||||
value = string.Empty;
|
||||
base [key] = value;
|
||||
@@ -763,8 +773,18 @@ namespace AppxPackage
|
||||
}
|
||||
public string At (string key)
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value)) throw new KeyNotFoundException ($"PRBaseApplication.At: key \"{key}\" not found");
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind) throw new KeyNotFoundException ($"MRApplication: cannot find key \"{key}\"");
|
||||
if (!EnablePri ()) return value;
|
||||
if (PriFileHelper.IsMsResourcePrefix (value))
|
||||
{
|
||||
@@ -776,8 +796,18 @@ namespace AppxPackage
|
||||
}
|
||||
public string NewAt (string key, bool toPriString)
|
||||
{
|
||||
string value;
|
||||
if (!TryGetValue (key, out value))
|
||||
string value = null;
|
||||
var isfind = false;
|
||||
foreach (var kv in this)
|
||||
{
|
||||
if (kv.Key?.Trim ()?.ToLowerInvariant () == key?.Trim ()?.ToLowerInvariant ())
|
||||
{
|
||||
value = kv.Value;
|
||||
isfind = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isfind)
|
||||
{
|
||||
value = string.Empty;
|
||||
base [key] = value;
|
||||
@@ -1780,6 +1810,753 @@ namespace AppxPackage
|
||||
UsePri = parsePri;
|
||||
}
|
||||
}
|
||||
public bool SaveJsonFileCS (string savefilepath)
|
||||
{
|
||||
try
|
||||
{
|
||||
#region
|
||||
using (var fs = File.Create (savefilepath))
|
||||
{
|
||||
using (var zos = new ZipOutputStream (fs))
|
||||
{
|
||||
zos.SetLevel (9);
|
||||
bool parsePri = UsePri;
|
||||
bool usePri = EnablePri;
|
||||
try
|
||||
{
|
||||
UsePri = false;
|
||||
EnablePri = false;
|
||||
object packageInfo = null;
|
||||
#region file
|
||||
var typestr = "unknown";
|
||||
switch (Type)
|
||||
{
|
||||
case PackageType.Appx: typestr = "appx"; break;
|
||||
case PackageType.Bundle: typestr = "bundle"; break;
|
||||
default:
|
||||
case PackageType.Unknown: typestr = "unknown"; break;
|
||||
}
|
||||
var rolestr = "unknown";
|
||||
switch (Role)
|
||||
{
|
||||
case PackageRole.Application: rolestr = "application"; break;
|
||||
case PackageRole.Framework: rolestr = "framework"; break;
|
||||
case PackageRole.Resource: rolestr = "resource"; break;
|
||||
default:
|
||||
case PackageRole.Unknown: rolestr = "unknown"; break;
|
||||
}
|
||||
var pkgfile = new {
|
||||
path = FilePath,
|
||||
valid = IsValid,
|
||||
type = typestr,
|
||||
role = rolestr
|
||||
};
|
||||
#endregion
|
||||
#region id
|
||||
var id = Identity;
|
||||
var pkgid = new {
|
||||
name = id.Name,
|
||||
publisher = id.Publisher,
|
||||
version = id.Version.ToString (),
|
||||
realVersion = id.RealVersion.ToString (),
|
||||
architecture = id.ProcessArchitecture.Select (e => {
|
||||
switch (e)
|
||||
{
|
||||
case Architecture.ARM: return "arm";
|
||||
case Architecture.ARM64: return "arm64";
|
||||
case Architecture.Neutral: return "neutral";
|
||||
case Architecture.x64: return "x64";
|
||||
case Architecture.x86: return "x86";
|
||||
default:
|
||||
case Architecture.Unknown: return "unknown";
|
||||
}
|
||||
}).ToList (),
|
||||
familyName = id.FamilyName,
|
||||
fullName = id.FullName,
|
||||
resourceId = id.ResourceId
|
||||
};
|
||||
#endregion
|
||||
#region prerequistes
|
||||
var preq = Prerequisites;
|
||||
var pkgpreq = new {
|
||||
osMinVersion = preq.OSMinVersion.ToString (),
|
||||
osMaxVersionTested = preq.OSMaxVersionTested.ToString (),
|
||||
osMinVersionDescription = preq.OSMinVersionDescription,
|
||||
osMaxVersionTestedDescription = preq.OSMaxVersionDescription
|
||||
};
|
||||
#endregion
|
||||
#region resources
|
||||
var res = Resources;
|
||||
var pkgres = new {
|
||||
languages = res.Languages,
|
||||
scales = res.Scales,
|
||||
dxFeatureLevels = res.DXFeatures.Select (d => {
|
||||
switch (d)
|
||||
{
|
||||
case DXFeatureLevel.Level9: return 9;
|
||||
case DXFeatureLevel.Level10: return 10;
|
||||
case DXFeatureLevel.Level11: return 11;
|
||||
case DXFeatureLevel.Level12: return 12;
|
||||
default:
|
||||
case DXFeatureLevel.Unspecified: return -1;
|
||||
}
|
||||
}).ToList ()
|
||||
};
|
||||
#endregion
|
||||
#region capabilities
|
||||
var caps = Capabilities;
|
||||
var pkgcaps = new {
|
||||
capabilities = caps.Capabilities,
|
||||
deviceCapabilities = caps.DeviceCapabilities
|
||||
};
|
||||
#endregion
|
||||
#region dependencies
|
||||
var deps = Dependencies;
|
||||
var pkgdeps = Dependencies.Select (d => new {
|
||||
name = d.Name,
|
||||
publisher = d.Publisher,
|
||||
minVersion = d.Version.ToString ()
|
||||
}).ToList ();
|
||||
#endregion
|
||||
using (var priAllRes = new PriAllValuesReader (this))
|
||||
{
|
||||
priAllRes.Init ();
|
||||
#region prop
|
||||
var prop = Properties;
|
||||
var dispname = prop.DisplayName;
|
||||
var dispNameDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (dispname))
|
||||
dispNameDict = priAllRes.LocaleResourceAllValues (dispname);
|
||||
dispNameDict ["root"] = dispname;
|
||||
var desc = prop.Description;
|
||||
var descDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (desc))
|
||||
descDict = priAllRes.LocaleResourceAllValues (desc);
|
||||
descDict ["root"] = desc;
|
||||
var disppub = prop.Publisher;
|
||||
var dispPubDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (disppub))
|
||||
dispPubDict = priAllRes.LocaleResourceAllValues (disppub);
|
||||
dispPubDict ["root"] = disppub;
|
||||
var logoList = priAllRes.FileResourceAllValues (prop.Logo, zos)
|
||||
.Select (kv => {
|
||||
string contrast = "";
|
||||
switch (kv.Key.Contrast)
|
||||
{
|
||||
case PriResourceKey.PriContrast.None: contrast = ""; break;
|
||||
case PriResourceKey.PriContrast.Black: contrast = "black"; break;
|
||||
case PriResourceKey.PriContrast.White: contrast = "white"; break;
|
||||
case PriResourceKey.PriContrast.High: contrast = "high"; break;
|
||||
case PriResourceKey.PriContrast.Low: contrast = "low"; break;
|
||||
}
|
||||
|
||||
if (kv.Key.IsTargetSize)
|
||||
{
|
||||
return new {
|
||||
targetSize = kv.Key.Value,
|
||||
contrast = contrast,
|
||||
name = kv.Value
|
||||
};
|
||||
}
|
||||
else if (kv.Key.IsScale)
|
||||
{
|
||||
return new {
|
||||
scale = kv.Key.Value,
|
||||
contrast = contrast,
|
||||
name = kv.Value
|
||||
};
|
||||
}
|
||||
else if (kv.Key.IsString)
|
||||
{
|
||||
return new {
|
||||
language = kv.Key.Value,
|
||||
name = kv.Value
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return (dynamic)null; // 不满足条件时返回 null
|
||||
}
|
||||
})
|
||||
.Where (item => item != null) // 过滤掉 null
|
||||
.ToList ();
|
||||
var pkgprop = new {
|
||||
displayName = dispNameDict,
|
||||
publisherDisplayName = dispPubDict,
|
||||
description = descDict,
|
||||
logo = logoList,
|
||||
framework = prop.Framework,
|
||||
resourcePackage = prop.ResourcePackage
|
||||
};
|
||||
#endregion
|
||||
#region apps
|
||||
var apps = Applications;
|
||||
var pkgapps = new List<object> ();
|
||||
foreach (var app in apps)
|
||||
{
|
||||
dynamic obj = new ExpandoObject ();
|
||||
var dict = (IDictionary<string, object>)obj;
|
||||
foreach (var kv in app)
|
||||
{
|
||||
if (Utils.AppFileProperties.Contains (kv.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
dict [Utils.PascalToCamel (kv.Key)] = priAllRes.FileResourceAllValues (kv.Value, zos)
|
||||
.Select (skv => {
|
||||
string contrast = "";
|
||||
switch (skv.Key.Contrast)
|
||||
{
|
||||
case PriResourceKey.PriContrast.None: contrast = ""; break;
|
||||
case PriResourceKey.PriContrast.Black: contrast = "black"; break;
|
||||
case PriResourceKey.PriContrast.White: contrast = "white"; break;
|
||||
case PriResourceKey.PriContrast.High: contrast = "high"; break;
|
||||
case PriResourceKey.PriContrast.Low: contrast = "low"; break;
|
||||
}
|
||||
|
||||
if (skv.Key.IsTargetSize)
|
||||
{
|
||||
return new {
|
||||
targetSize = skv.Key.Value,
|
||||
contrast = contrast,
|
||||
name = skv.Value
|
||||
};
|
||||
}
|
||||
else if (skv.Key.IsScale)
|
||||
{
|
||||
return new {
|
||||
scale = skv.Key.Value,
|
||||
contrast = contrast,
|
||||
name = skv.Value
|
||||
};
|
||||
}
|
||||
else if (skv.Key.IsString)
|
||||
{
|
||||
return new {
|
||||
language = skv.Key.Value,
|
||||
name = skv.Value
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return (dynamic)null; // 不满足条件时返回 null
|
||||
}
|
||||
}).Where (item => item != null).ToList ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PriFileHelper.IsMsResourcePrefix (kv.Value))
|
||||
{
|
||||
var itemDict = new Dictionary<string, string> ();
|
||||
itemDict = priAllRes.LocaleResourceAllValues (kv.Value);
|
||||
itemDict ["root"] = kv.Value;
|
||||
dict [Utils.PascalToCamel (kv.Key)] = itemDict;
|
||||
}
|
||||
else
|
||||
{
|
||||
dict [Utils.PascalToCamel (kv.Key)] = kv.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
pkgapps.Add (obj);
|
||||
}
|
||||
#endregion
|
||||
packageInfo = new {
|
||||
file = pkgfile,
|
||||
identity = pkgid,
|
||||
properties = pkgprop,
|
||||
resources = pkgres,
|
||||
prerequisites = pkgpreq,
|
||||
applications = pkgapps,
|
||||
capabilities = pkgcaps,
|
||||
dependencies = pkgdeps
|
||||
};
|
||||
}
|
||||
var jsonstr = Newtonsoft.Json.JsonConvert.SerializeObject (packageInfo, Newtonsoft.Json.Formatting.None);
|
||||
var ze = new ZipEntry ("info.json");
|
||||
ze.DateTime = DateTime.Now;
|
||||
zos.PutNextEntry (ze);
|
||||
var bytes = Encoding.UTF8.GetBytes (jsonstr);
|
||||
zos.Write (bytes, 0, bytes.Length);
|
||||
zos.CloseEntry ();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EnablePri = usePri;
|
||||
UsePri = parsePri;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SaveXmlFileCS (string savefilepath)
|
||||
{
|
||||
try
|
||||
{
|
||||
#region
|
||||
using (var fs = File.Create (savefilepath))
|
||||
{
|
||||
using (var zos = new ZipOutputStream (fs))
|
||||
{
|
||||
zos.SetLevel (9);
|
||||
bool parsePri = UsePri;
|
||||
bool usePri = EnablePri;
|
||||
try
|
||||
{
|
||||
UsePri = false;
|
||||
EnablePri = false;
|
||||
var xml = new XmlDocument ();
|
||||
var decl = xml.CreateXmlDeclaration ("1.0", "utf-8", "yes");
|
||||
xml.AppendChild (decl);
|
||||
var root = xml.CreateElement ("Package");
|
||||
xml.AppendChild (root);
|
||||
#region file
|
||||
var typestr = "unknown";
|
||||
switch (Type)
|
||||
{
|
||||
case PackageType.Appx: typestr = "appx"; break;
|
||||
case PackageType.Bundle: typestr = "bundle"; break;
|
||||
default:
|
||||
case PackageType.Unknown: typestr = "unknown"; break;
|
||||
}
|
||||
var rolestr = "unknown";
|
||||
switch (Role)
|
||||
{
|
||||
case PackageRole.Application: rolestr = "application"; break;
|
||||
case PackageRole.Framework: rolestr = "framework"; break;
|
||||
case PackageRole.Resource: rolestr = "resource"; break;
|
||||
default:
|
||||
case PackageRole.Unknown: rolestr = "unknown"; break;
|
||||
}
|
||||
{
|
||||
var nodefile = xml.CreateElement ("File");
|
||||
var nodefilepath = xml.CreateElement ("Path");
|
||||
nodefilepath.InnerText = FilePath;
|
||||
var nodefilevalid = xml.CreateElement ("Valid");
|
||||
nodefilevalid.InnerText = IsValid ? "true" : "false";
|
||||
var nodefiletype = xml.CreateElement ("Type");
|
||||
nodefiletype.InnerText = typestr;
|
||||
var nodefilerole = xml.CreateElement ("Role");
|
||||
nodefilerole.InnerText = rolestr;
|
||||
nodefile.AppendChild (nodefilepath);
|
||||
nodefile.AppendChild (nodefilevalid);
|
||||
nodefile.AppendChild (nodefiletype);
|
||||
nodefile.AppendChild (nodefilerole);
|
||||
root.AppendChild (nodefile);
|
||||
}
|
||||
#endregion
|
||||
#region id
|
||||
var id = Identity;
|
||||
{
|
||||
var nodeid = xml.CreateElement ("Identity");
|
||||
var nodeidname = xml.CreateElement ("Name");
|
||||
nodeidname.InnerText = id.Name;
|
||||
var nodeidpub = xml.CreateElement ("Publisher");
|
||||
nodeidpub.InnerText = id.Publisher;
|
||||
var nodeidver = xml.CreateElement ("Version");
|
||||
nodeidver.InnerText = id.Version.ToString ();
|
||||
var nodeidrealver = xml.CreateElement ("RealVersion");
|
||||
nodeidrealver.InnerText = id.RealVersion.ToString ();
|
||||
var nodeidarchs = xml.CreateElement ("ProcessorArchitectures");
|
||||
foreach (var a in id.ProcessArchitecture)
|
||||
{
|
||||
var astr = "";
|
||||
switch (a)
|
||||
{
|
||||
case Architecture.ARM: astr = "arm"; break;
|
||||
case Architecture.ARM64: astr = "arm64"; break;
|
||||
case Architecture.Neutral: astr = "neutral"; break;
|
||||
case Architecture.x64: astr = "x64"; break;
|
||||
case Architecture.x86: astr = "x86"; break;
|
||||
default:
|
||||
case Architecture.Unknown: astr = "unknown"; break;
|
||||
}
|
||||
var nodeidarch = xml.CreateElement ("ProcessorArchitecture");
|
||||
nodeidarch.SetAttribute ("Value", astr);
|
||||
nodeidarchs.AppendChild (nodeidarch);
|
||||
}
|
||||
var nodeidfamily = xml.CreateElement ("FamilyName");
|
||||
nodeidfamily.InnerText = id.FamilyName;
|
||||
var nodeidfull = xml.CreateElement ("FullName");
|
||||
nodeidfull.InnerText = id.FullName;
|
||||
var nodeidresid = xml.CreateElement ("ResourceId");
|
||||
nodeidresid.InnerText = id.ResourceId;
|
||||
nodeid.AppendChild (nodeidname);
|
||||
nodeid.AppendChild (nodeidpub);
|
||||
nodeid.AppendChild (nodeidver);
|
||||
nodeid.AppendChild (nodeidrealver);
|
||||
nodeid.AppendChild (nodeidarchs);
|
||||
nodeid.AppendChild (nodeidfamily);
|
||||
nodeid.AppendChild (nodeidfamily);
|
||||
nodeid.AppendChild (nodeidresid);
|
||||
root.AppendChild (nodeid);
|
||||
}
|
||||
#endregion
|
||||
using (var priAllRes = new PriAllValuesReader (this))
|
||||
{
|
||||
priAllRes.Init ();
|
||||
#region prop
|
||||
var prop = Properties;
|
||||
var dispname = prop.DisplayName;
|
||||
var dispNameDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (dispname))
|
||||
dispNameDict = priAllRes.LocaleResourceAllValues (dispname);
|
||||
dispNameDict ["root"] = dispname;
|
||||
var desc = prop.Description;
|
||||
var descDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (desc))
|
||||
descDict = priAllRes.LocaleResourceAllValues (desc);
|
||||
descDict ["root"] = desc;
|
||||
var disppub = prop.Publisher;
|
||||
var dispPubDict = new Dictionary<string, string> ();
|
||||
if (PriFileHelper.IsMsResourcePrefix (disppub))
|
||||
dispPubDict = priAllRes.LocaleResourceAllValues (disppub);
|
||||
dispPubDict ["root"] = disppub;
|
||||
{
|
||||
var nodeprop = xml.CreateElement ("Properties");
|
||||
var nodepropname = xml.CreateElement ("DisplayName");
|
||||
foreach (var kv in dispNameDict)
|
||||
{
|
||||
var nodelocale = xml.CreateElement ("LocaleResource");
|
||||
nodelocale.SetAttribute ("Lang", kv.Key);
|
||||
nodelocale.InnerText = kv.Value;
|
||||
nodepropname.AppendChild (nodelocale);
|
||||
}
|
||||
var nodeproppub = xml.CreateElement ("PublisherDisplayName");
|
||||
foreach (var kv in dispPubDict)
|
||||
{
|
||||
var nodelocale = xml.CreateElement ("LocaleResource");
|
||||
nodelocale.SetAttribute ("Lang", kv.Key);
|
||||
nodelocale.InnerText = kv.Value;
|
||||
nodeproppub.AppendChild (nodelocale);
|
||||
}
|
||||
var nodepropdesc = xml.CreateElement ("Description");
|
||||
foreach (var kv in descDict)
|
||||
{
|
||||
var nodelocale = xml.CreateElement ("LocaleResource");
|
||||
nodelocale.SetAttribute ("Lang", kv.Key);
|
||||
nodelocale.InnerText = kv.Value;
|
||||
nodepropdesc.AppendChild (nodelocale);
|
||||
}
|
||||
var nodeproplogo = xml.CreateElement ("Logo");
|
||||
foreach (var kv in priAllRes.FileResourceAllValues (prop.Logo, zos))
|
||||
{
|
||||
#region logo
|
||||
var key = kv.Key;
|
||||
var value = kv.Value;
|
||||
var nodefile = xml.CreateElement ("File");
|
||||
var attrName = xml.CreateAttribute ("Name");
|
||||
attrName.Value = value;
|
||||
nodefile.Attributes.Append (attrName);
|
||||
if (key.Contrast != PriResourceKey.PriContrast.None)
|
||||
{
|
||||
string contrast = "";
|
||||
switch (key.Contrast)
|
||||
{
|
||||
case PriResourceKey.PriContrast.Black: contrast = "black"; break;
|
||||
case PriResourceKey.PriContrast.White: contrast = "white"; break;
|
||||
case PriResourceKey.PriContrast.High: contrast = "high"; break;
|
||||
case PriResourceKey.PriContrast.Low: contrast = "low"; break;
|
||||
}
|
||||
var attrContrast = xml.CreateAttribute ("Contrast");
|
||||
attrContrast.Value = contrast;
|
||||
nodefile.Attributes.Append (attrContrast);
|
||||
}
|
||||
if (key.IsTargetSize)
|
||||
{
|
||||
var attr = xml.CreateAttribute ("TargetSize");
|
||||
attr.Value = key.Value.ToString ();
|
||||
nodefile.Attributes.Append (attr);
|
||||
}
|
||||
else if (key.IsScale)
|
||||
{
|
||||
var attr = xml.CreateAttribute ("Scale");
|
||||
attr.Value = key.Value.ToString ();
|
||||
nodefile.Attributes.Append (attr);
|
||||
}
|
||||
else if (key.IsString)
|
||||
{
|
||||
var attr = xml.CreateAttribute ("Language");
|
||||
attr.Value = key.Value.ToString ();
|
||||
nodefile.Attributes.Append (attr);
|
||||
}
|
||||
nodeproplogo.AppendChild (nodefile);
|
||||
#endregion
|
||||
}
|
||||
var nodepropfrw = xml.CreateElement ("Framework");
|
||||
nodepropfrw.InnerText = prop.Framework ? "true" : "false";
|
||||
var nodepropres = xml.CreateElement ("ResourcePackage");
|
||||
nodepropres.InnerText = prop.ResourcePackage ? "true" : "false";
|
||||
nodeprop.AppendChild (nodepropname);
|
||||
nodeprop.AppendChild (nodeproppub);
|
||||
nodeprop.AppendChild (nodepropdesc);
|
||||
nodeprop.AppendChild (nodeproplogo);
|
||||
nodeprop.AppendChild (nodepropfrw);
|
||||
nodeprop.AppendChild (nodepropres);
|
||||
root.AppendChild (nodeprop);
|
||||
}
|
||||
#endregion
|
||||
#region apps
|
||||
// Applications 节点
|
||||
XmlElement nodeApplications = xml.CreateElement ("Applications");
|
||||
|
||||
foreach (var app in Applications)
|
||||
{
|
||||
XmlElement nodeApp = xml.CreateElement ("Application");
|
||||
|
||||
// AppUserModelId 作为属性
|
||||
string aumidObj = app ["AppUserModelId"];
|
||||
if (aumidObj == null || aumidObj.Trim ().Length == 0) aumidObj = app ["AppUserModelID"];
|
||||
nodeApp.SetAttribute ("AppUserModelId", aumidObj);
|
||||
foreach (var kv in app)
|
||||
{
|
||||
string key = kv.Key;
|
||||
string value = kv.Value;
|
||||
|
||||
// 跳过 AppUserModelId,因为已经作为属性
|
||||
if (string.Equals (key, "AppUserModelId", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
XmlElement nodeProp = xml.CreateElement (key);
|
||||
|
||||
// 文件资源类型
|
||||
if (Utils.AppFileProperties.Contains (key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var skv in priAllRes.FileResourceAllValues (value, zos))
|
||||
{
|
||||
var fileKey = skv.Key;
|
||||
var fileValue = skv.Value;
|
||||
|
||||
XmlElement nodeFile = xml.CreateElement ("File");
|
||||
|
||||
// Name 必有
|
||||
XmlAttribute attrName = xml.CreateAttribute ("Name");
|
||||
attrName.Value = fileValue;
|
||||
nodeFile.Attributes.Append (attrName);
|
||||
|
||||
// Contrast 可选
|
||||
if (fileKey.Contrast != PriResourceKey.PriContrast.None)
|
||||
{
|
||||
string contrast = "";
|
||||
switch (fileKey.Contrast)
|
||||
{
|
||||
case PriResourceKey.PriContrast.Black: contrast = "black"; break;
|
||||
case PriResourceKey.PriContrast.White: contrast = "white"; break;
|
||||
case PriResourceKey.PriContrast.High: contrast = "high"; break;
|
||||
case PriResourceKey.PriContrast.Low: contrast = "low"; break;
|
||||
}
|
||||
if (!string.IsNullOrEmpty (contrast))
|
||||
{
|
||||
XmlAttribute attrContrast = xml.CreateAttribute ("Contrast");
|
||||
attrContrast.Value = contrast;
|
||||
nodeFile.Attributes.Append (attrContrast);
|
||||
}
|
||||
}
|
||||
|
||||
// TargetSize
|
||||
if (fileKey.IsTargetSize)
|
||||
{
|
||||
XmlAttribute attrTS = xml.CreateAttribute ("TargetSize");
|
||||
attrTS.Value = fileKey.Value.ToString ();
|
||||
nodeFile.Attributes.Append (attrTS);
|
||||
}
|
||||
// Scale
|
||||
else if (fileKey.IsScale)
|
||||
{
|
||||
XmlAttribute attrS = xml.CreateAttribute ("Scale");
|
||||
attrS.Value = fileKey.Value.ToString ();
|
||||
nodeFile.Attributes.Append (attrS);
|
||||
}
|
||||
// Language
|
||||
else if (fileKey.IsString)
|
||||
{
|
||||
XmlAttribute attrL = xml.CreateAttribute ("Lang");
|
||||
attrL.Value = fileKey.Value.ToString ();
|
||||
nodeFile.Attributes.Append (attrL);
|
||||
}
|
||||
nodeProp.AppendChild (nodeFile);
|
||||
}
|
||||
}
|
||||
// 多语言资源
|
||||
else if (PriFileHelper.IsMsResourcePrefix (value))
|
||||
{
|
||||
Dictionary<string, string> localeDict = priAllRes.LocaleResourceAllValues (value);
|
||||
localeDict ["root"] = value.ToString ();
|
||||
|
||||
foreach (KeyValuePair<string, string> lkv in localeDict)
|
||||
{
|
||||
XmlElement nodeLocale = xml.CreateElement ("LocaleResource");
|
||||
nodeLocale.SetAttribute ("Lang", lkv.Key);
|
||||
nodeLocale.InnerText = lkv.Value;
|
||||
nodeProp.AppendChild (nodeLocale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeProp.InnerText = value.ToString ();
|
||||
}
|
||||
|
||||
nodeApp.AppendChild (nodeProp);
|
||||
}
|
||||
|
||||
nodeApplications.AppendChild (nodeApp);
|
||||
}
|
||||
root.AppendChild (nodeApplications);
|
||||
#endregion
|
||||
}
|
||||
#region prerequistes
|
||||
var preq = Prerequisites;
|
||||
{
|
||||
var nodepreq = xml.CreateElement ("Prerequisites");
|
||||
var nodeosmin = xml.CreateElement ("OSMinVersion");
|
||||
nodeosmin.InnerText = preq.OSMinVersion.ToString ();
|
||||
nodeosmin.SetAttribute ("Description", preq.OSMinVersionDescription);
|
||||
var nodeosmaxt = xml.CreateElement ("OSMaxVersionTested");
|
||||
nodeosmaxt.InnerText = preq.OSMaxVersionTested.ToString ();
|
||||
nodeosmaxt.SetAttribute ("Description", preq.OSMaxVersionDescription);
|
||||
nodepreq.AppendChild (nodeosmin);
|
||||
nodepreq.AppendChild (nodeosmaxt);
|
||||
root.AppendChild (nodepreq);
|
||||
}
|
||||
#endregion
|
||||
#region resources
|
||||
XmlElement nodeResources = xml.CreateElement ("Resources");
|
||||
var res = Resources;
|
||||
if (res.Languages != null)
|
||||
{
|
||||
foreach (var lang in res.Languages)
|
||||
{
|
||||
XmlElement nodeRes = xml.CreateElement ("Resource");
|
||||
XmlAttribute attrLang = xml.CreateAttribute ("Language");
|
||||
attrLang.Value = lang;
|
||||
nodeRes.Attributes.Append (attrLang);
|
||||
nodeResources.AppendChild (nodeRes);
|
||||
}
|
||||
}
|
||||
if (res.Scales != null)
|
||||
{
|
||||
foreach (var scale in res.Scales)
|
||||
{
|
||||
XmlElement nodeRes = xml.CreateElement ("Resource");
|
||||
XmlAttribute attrScale = xml.CreateAttribute ("Scale");
|
||||
attrScale.Value = scale.ToString ();
|
||||
nodeRes.Attributes.Append (attrScale);
|
||||
nodeResources.AppendChild (nodeRes);
|
||||
}
|
||||
}
|
||||
if (res.DXFeatures != null)
|
||||
{
|
||||
foreach (var d in res.DXFeatures)
|
||||
{
|
||||
int level = -1;
|
||||
switch (d)
|
||||
{
|
||||
case DXFeatureLevel.Level9: level = 9; break;
|
||||
case DXFeatureLevel.Level10: level = 10; break;
|
||||
case DXFeatureLevel.Level11: level = 11; break;
|
||||
case DXFeatureLevel.Level12: level = 12; break;
|
||||
case DXFeatureLevel.Unspecified:
|
||||
default: level = -1; break;
|
||||
}
|
||||
XmlElement nodeRes = xml.CreateElement ("Resource");
|
||||
XmlAttribute attrDX = xml.CreateAttribute ("DXFeatureLevel");
|
||||
attrDX.Value = level.ToString ();
|
||||
nodeRes.Attributes.Append (attrDX);
|
||||
nodeResources.AppendChild (nodeRes);
|
||||
}
|
||||
}
|
||||
root.AppendChild (nodeResources);
|
||||
#endregion
|
||||
#region capabilities
|
||||
XmlElement nodeCapabilities = xml.CreateElement ("Capabilities");
|
||||
var caps = Capabilities;
|
||||
if (caps.Capabilities != null)
|
||||
{
|
||||
foreach (var cap in caps.Capabilities)
|
||||
{
|
||||
XmlElement nodeCap = xml.CreateElement ("Capability");
|
||||
XmlAttribute attrName = xml.CreateAttribute ("Name");
|
||||
attrName.Value = cap;
|
||||
nodeCap.Attributes.Append (attrName);
|
||||
nodeCapabilities.AppendChild (nodeCap);
|
||||
}
|
||||
}
|
||||
if (caps.DeviceCapabilities != null)
|
||||
{
|
||||
foreach (var devCap in caps.DeviceCapabilities)
|
||||
{
|
||||
XmlElement nodeDevCap = xml.CreateElement ("DeviceCapability");
|
||||
XmlAttribute attrName = xml.CreateAttribute ("Name");
|
||||
attrName.Value = devCap;
|
||||
nodeDevCap.Attributes.Append (attrName);
|
||||
nodeCapabilities.AppendChild (nodeDevCap);
|
||||
}
|
||||
}
|
||||
root.AppendChild (nodeCapabilities);
|
||||
#endregion
|
||||
#region dependencies
|
||||
XmlElement nodeDependencies = xml.CreateElement ("Dependencies");
|
||||
var deps = Dependencies;
|
||||
if (deps != null)
|
||||
{
|
||||
foreach (var dep in deps)
|
||||
{
|
||||
XmlElement nodeDep = xml.CreateElement ("Dependency");
|
||||
XmlAttribute attrName = xml.CreateAttribute ("Name");
|
||||
attrName.Value = dep.Name ?? "";
|
||||
nodeDep.Attributes.Append (attrName);
|
||||
XmlAttribute attrPublisher = xml.CreateAttribute ("Publisher");
|
||||
attrPublisher.Value = dep.Publisher ?? "";
|
||||
nodeDep.Attributes.Append (attrPublisher);
|
||||
XmlAttribute attrMinVersion = xml.CreateAttribute ("MinVersion");
|
||||
attrMinVersion.Value = dep.Version != null ? dep.Version.ToString () : "";
|
||||
nodeDep.Attributes.Append (attrMinVersion);
|
||||
nodeDependencies.AppendChild (nodeDep);
|
||||
}
|
||||
}
|
||||
root.AppendChild (nodeDependencies);
|
||||
#endregion
|
||||
var ze = new ZipEntry ("info.xml");
|
||||
ze.DateTime = DateTime.Now;
|
||||
zos.PutNextEntry (ze);
|
||||
byte [] bytes;
|
||||
using (var ms = new MemoryStream ())
|
||||
{
|
||||
var settings = new XmlWriterSettings {
|
||||
Encoding = Encoding.UTF8,
|
||||
Indent = true,
|
||||
OmitXmlDeclaration = false
|
||||
};
|
||||
using (var writer = XmlWriter.Create (ms, settings))
|
||||
{
|
||||
xml.Save (writer);
|
||||
}
|
||||
bytes = ms.ToArray ();
|
||||
}
|
||||
zos.Write (bytes, 0, bytes.Length);
|
||||
zos.CloseEntry ();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EnablePri = usePri;
|
||||
UsePri = parsePri;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public void SaveJsonFile (string savefilepath, object resolve, object reject)
|
||||
{
|
||||
try
|
||||
@@ -2060,7 +2837,7 @@ namespace AppxPackage
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (reject != null) JSHelper.CallJS (reject, ex);
|
||||
if (reject != null) JSHelper.CallJS (reject, new DataUtils._I_Exception (ex));
|
||||
}
|
||||
}
|
||||
public void SaveJsonFileAsync (string savefilepath, object resolve, object reject)
|
||||
@@ -2531,7 +3308,7 @@ namespace AppxPackage
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (reject != null) JSHelper.CallJS (reject, ex);
|
||||
if (reject != null) JSHelper.CallJS (reject, new DataUtils._I_Exception (ex));
|
||||
}
|
||||
}
|
||||
public void SaveXmlFileAsync (string savefilepath, object resolve, object reject)
|
||||
|
||||
230
PkgCLI/CliParsing.cs
Normal file
230
PkgCLI/CliParsing.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PkgCLI
|
||||
{
|
||||
public class NormalizeStringComparer: IEqualityComparer<string>
|
||||
{
|
||||
public bool Equals (string x, string y) => Polyfill.NEquals (x, y);
|
||||
public int GetHashCode (string obj) => obj.NNormalize ().GetHashCode ();
|
||||
}
|
||||
public class NormalizeCharacterComparer: IEqualityComparer<char>
|
||||
{
|
||||
public bool Equals (char x, char y) => char.ToLowerInvariant (x) == char.ToLowerInvariant (y);
|
||||
public int GetHashCode (char obj) => char.ToLowerInvariant (obj).GetHashCode ();
|
||||
}
|
||||
public class StringNSet: HashSet<string>
|
||||
{
|
||||
public StringNSet () : base (new NormalizeStringComparer ()) { }
|
||||
public StringNSet (IEnumerable<string> list) : base (list, new NormalizeStringComparer ()) { }
|
||||
}
|
||||
public class CharNSet: HashSet<char>
|
||||
{
|
||||
public CharNSet () : base (new NormalizeCharacterComparer ()) { }
|
||||
public CharNSet (IEnumerable<char> list) : base (list, new NormalizeCharacterComparer ()) { }
|
||||
}
|
||||
public static class CliParsingConst
|
||||
{
|
||||
public static readonly char [] defaultPrefixs = new char [] { '/', '-' };
|
||||
public static readonly char [] defaultPostfixs = new char [] { '=', ':' };
|
||||
public static readonly string [] emptyStringArray = new string [] { };
|
||||
}
|
||||
public class CmdParamName: IEquatable<CmdParamName>, IComparable<CmdParamName>, IDisposable
|
||||
{
|
||||
private string _id = "";
|
||||
private string _name = "";
|
||||
/// <summary>
|
||||
/// 命令唯一标识,但不作为命令名,仅用于内部识别。命令 ID 可以作为命令名或别名。
|
||||
/// </summary>
|
||||
public string Id { get { return _id.NNormalize (); } set { _id = value; } }
|
||||
/// <summary>
|
||||
/// 命令名,唯一。命令名不能与别名重复。首字符不能为前缀中的字符,尾字符不能为后缀中的字符。
|
||||
/// </summary>
|
||||
public string Name { get { return _name.NNormalize (); } set { _name = value; } }
|
||||
/// <summary>
|
||||
/// 命令别名,唯一。不能与命令名重复。首字符不能为前缀中的字符,尾字符不能为后缀中的字符。
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public StringNSet Aliases { get; private set; } = new StringNSet ();
|
||||
/// <summary>
|
||||
/// 命令前缀,为一个字符,标点符号。不能为命令名或别名的首字符。
|
||||
/// </summary>
|
||||
public CharNSet Prefixs { get; set; } = new CharNSet (CliParsingConst.defaultPrefixs);
|
||||
/// <summary>
|
||||
/// 命令后缀,为一个字符,标点符号。不能为命令名或别名的尾字符。
|
||||
/// </summary>
|
||||
public CharNSet Postfixs { get; set; } = new CharNSet (CliParsingConst.defaultPostfixs);
|
||||
public bool Equals (CmdParamName other)
|
||||
{
|
||||
if (other == null) return false;
|
||||
if (ReferenceEquals (this, other)) return true;
|
||||
return string.Equals (this.Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
public int CompareTo (CmdParamName other)
|
||||
{
|
||||
if (other == null) return 1;
|
||||
if (ReferenceEquals (this, other)) return 0;
|
||||
return string.Compare (this.Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
public override bool Equals (object obj) => Equals (obj as CmdParamName);
|
||||
public override int GetHashCode () => this.Id?.GetHashCode () ?? 0;
|
||||
public bool ParamContains (string param)
|
||||
{
|
||||
var ret = Name.NEquals (param);
|
||||
if (!ret)
|
||||
{
|
||||
foreach (var alias in Aliases)
|
||||
{
|
||||
if (alias.NEquals (param)) return true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
public void Dispose ()
|
||||
{
|
||||
Aliases?.Clear ();
|
||||
Prefixs?.Clear ();
|
||||
}
|
||||
public CmdParamName (string id, string name, IEnumerable<string> aliases, IEnumerable<char> prefixs, IEnumerable<char> postfixs)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Aliases = new StringNSet (aliases);
|
||||
Prefixs = new CharNSet (prefixs);
|
||||
Postfixs = new CharNSet (postfixs);
|
||||
}
|
||||
public CmdParamName (string name, IEnumerable<string> aliases) : this (name, name, aliases, CliParsingConst.defaultPrefixs, CliParsingConst.defaultPostfixs) { }
|
||||
public CmdParamName (string name) : this (name, CliParsingConst.emptyStringArray) { }
|
||||
public CmdParamName () { }
|
||||
}
|
||||
public class CommandParam: IEquatable<string>
|
||||
{
|
||||
private string _id = "";
|
||||
public string Id { get { return _id.NNormalize (); } set { _id = value; } }
|
||||
public string Value = "";
|
||||
public bool Equals (string other)
|
||||
{
|
||||
return (_id ?? "").NEquals (other);
|
||||
}
|
||||
public override bool Equals (object obj)
|
||||
{
|
||||
if (obj is string) return Equals (obj as string);
|
||||
else if (obj is CommandParam) return Equals ((obj as CommandParam).Id);
|
||||
else return base.Equals (obj);
|
||||
}
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
return Id.GetHashCode ();
|
||||
}
|
||||
}
|
||||
public class CliParsing: IDisposable
|
||||
{
|
||||
public HashSet<CmdParamName> Params { get; set; } = new HashSet<CmdParamName> ();
|
||||
public void Dispose ()
|
||||
{
|
||||
Params = null;
|
||||
}
|
||||
public List<CommandParam> Parse (string [] args)
|
||||
{
|
||||
var ret = new List<CommandParam> ();
|
||||
CommandParam last = new CommandParam ();
|
||||
for (long i = 0; i < args.LongLength; i++)
|
||||
{
|
||||
var arg = args [i]?.Trim () ?? "";
|
||||
var item = args [i]?.NNormalize () ?? "";
|
||||
if (string.IsNullOrWhiteSpace (item)) continue;
|
||||
var first = item [0];
|
||||
bool isfind = false;
|
||||
foreach (var param in Params)
|
||||
{
|
||||
if (param.Prefixs.Contains (first))
|
||||
{
|
||||
var minser = param.Postfixs.Select (e => {
|
||||
var index = item.IndexOf (e);
|
||||
return index == -1 ? int.MaxValue : index;
|
||||
}).Min ();
|
||||
string paramPart, postfixPart;
|
||||
if (minser == int.MaxValue)
|
||||
{
|
||||
paramPart = arg.Substring (1);
|
||||
postfixPart = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
paramPart = arg.Substring (1, minser - 1);
|
||||
postfixPart = arg.Substring (minser + 1);
|
||||
}
|
||||
if (param.ParamContains (paramPart))
|
||||
{
|
||||
isfind = true;
|
||||
var cmdParam = new CommandParam ();
|
||||
cmdParam.Id = param.Id;
|
||||
if (!string.IsNullOrEmpty (postfixPart))
|
||||
cmdParam.Value = postfixPart;
|
||||
last = cmdParam;
|
||||
ret.Add (cmdParam);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isfind)
|
||||
{
|
||||
var valueparam = new CommandParam ();
|
||||
valueparam.Value = arg;
|
||||
ret.Add (valueparam);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
public static class CliPasingUtils
|
||||
{
|
||||
public static bool ParamContains (this List<CommandParam> cpl, string id)
|
||||
{
|
||||
foreach (var i in cpl)
|
||||
{
|
||||
if (i.Id.NEquals (id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool ParamContains (this List<CommandParam> cpl, CmdParamName param)
|
||||
{
|
||||
foreach (var i in cpl)
|
||||
{
|
||||
if (i.Id.NEquals (param.Id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool ParamsContainsOr (this List<CommandParam> cpl, params string [] ids)
|
||||
{
|
||||
foreach (var i in cpl)
|
||||
{
|
||||
foreach (var j in ids)
|
||||
{
|
||||
if (i.Id.NEquals (j)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool ParamsContainsAnd (this List<CommandParam> cpl, params string [] ids)
|
||||
{
|
||||
if (ids == null || ids.Length == 0) return true;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (!ParamContains (cpl, id)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static CommandParam GetFromId (this List <CommandParam> cpl, string id)
|
||||
{
|
||||
foreach (var c in cpl)
|
||||
{
|
||||
if (c.Id.NEquals (id)) return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -59,19 +63,29 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CliParsing.cs" />
|
||||
<Compile Include="Polyfill.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Text.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AppxPackage\AppxPackage.csproj">
|
||||
<Project>{bd681a4f-eb60-4bb8-90b5-65968fc7da59}</Project>
|
||||
<Name>AppxPackage</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DataUtils\DataUtils.csproj">
|
||||
<Project>{ffd3fd52-37a8-4f43-883c-de8d996cb0e0}</Project>
|
||||
<Name>DataUtils</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\PrivateInit\PrivateInit.csproj">
|
||||
<Project>{8e708d9a-6325-4aa9-b5a5-d1b5eca8eef7}</Project>
|
||||
<Name>PrivateInit</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
979
PkgCLI/Polyfill.cs
Normal file
979
PkgCLI/Polyfill.cs
Normal file
@@ -0,0 +1,979 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using AppxPackage;
|
||||
using AppxPackage.Info;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PkgCLI
|
||||
{
|
||||
public static class Polyfill
|
||||
{
|
||||
static public bool NEquals (this string s1, string s2)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace (s1) && string.IsNullOrWhiteSpace (s2)) return true;
|
||||
else return s1?.Trim ()?.ToLowerInvariant () == s2?.Trim ()?.ToLowerInvariant ();
|
||||
}
|
||||
static public string NNormalize (this string s1)
|
||||
{
|
||||
return s1?.Trim ()?.ToLowerInvariant ();
|
||||
}
|
||||
static public string Join<T> (this IEnumerable<T> ie, string divide = ", ")
|
||||
{
|
||||
var ret = "";
|
||||
for (var i = 0; i < ie.Count (); i++)
|
||||
{
|
||||
if (i != 0) ret += divide;
|
||||
var item = ie.ElementAt (i);
|
||||
if (item == null) ret += "(null)";
|
||||
else ret += item.ToString ();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将对象通过指定的选择器转换为目标类型。
|
||||
/// 类似于 LINQ 的 Select,但适用于单一对象。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型</typeparam>
|
||||
/// <param name="obj">源对象</param>
|
||||
/// <param name="selector">转换委托,输入源对象,输出目标对象</param>
|
||||
/// <returns>转换后的结果</returns>
|
||||
public static T ToOther<T> (this object obj, Func<object, T> selector)
|
||||
{
|
||||
if (selector == null)
|
||||
throw new ArgumentNullException (nameof (selector));
|
||||
return selector (obj);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将字典格式化为对齐的文本,每个键值对一行,分隔符垂直对齐。
|
||||
/// </summary>
|
||||
/// <param name="dict">要格式化的字典</param>
|
||||
/// <param name="separator">分隔符(如 ":", "="),默认为 ":"</param>
|
||||
/// <param name="indent">每行前的缩进字符串,默认为空</param>
|
||||
/// <param name="sortKeys">是否按键名排序(不区分大小写),默认为 false</param>
|
||||
/// <returns>格式化后的字符串,例如:
|
||||
/// Name : Alice
|
||||
/// Age : 30
|
||||
/// </returns>
|
||||
public static string FormatDictionaryAligned (
|
||||
this IDictionary<string, string> dict,
|
||||
string separator = ":",
|
||||
string indent = "",
|
||||
bool sortKeys = false)
|
||||
{
|
||||
if (dict == null || dict.Count == 0)
|
||||
return string.Empty;
|
||||
var keys = sortKeys
|
||||
? dict.Keys.OrderBy (k => k, StringComparer.OrdinalIgnoreCase).ToList ()
|
||||
: dict.Keys.ToList ();
|
||||
int maxKeyLength = keys.Max (k => k.Length);
|
||||
var sb = new StringBuilder ();
|
||||
foreach (string key in keys)
|
||||
{
|
||||
string value = dict [key] ?? string.Empty;
|
||||
sb.AppendLine ($"{indent}{key.PadRight (maxKeyLength)} {separator} {value}");
|
||||
}
|
||||
return sb.ToString ().TrimEnd (Environment.NewLine.ToCharArray ());
|
||||
}
|
||||
}
|
||||
public static class PackageReaderExt
|
||||
{
|
||||
static public object GetJsonObjectForCli (this PackageReader pr)
|
||||
{
|
||||
var id = pr.Identity;
|
||||
var prop = pr.Properties;
|
||||
var pre = pr.Prerequisites;
|
||||
var apps = pr.Applications;
|
||||
var caps = pr.Capabilities;
|
||||
var deps = pr.Dependencies;
|
||||
dynamic obj = new {
|
||||
valid = pr.IsValid,
|
||||
type = pr.Type.ToOther (e => {
|
||||
switch ((AppxPackage.Info.PackageType)e)
|
||||
{
|
||||
case AppxPackage.Info.PackageType.Appx: return "appx";
|
||||
case AppxPackage.Info.PackageType.Bundle: return "bundle";
|
||||
default:
|
||||
case AppxPackage.Info.PackageType.Unknown: return "unknown";
|
||||
}
|
||||
}),
|
||||
role = pr.Role.ToOther (o => {
|
||||
switch ((AppxPackage.Info.PackageRole)o)
|
||||
{
|
||||
case AppxPackage.Info.PackageRole.Application: return "application";
|
||||
case AppxPackage.Info.PackageRole.Framework: return "framework";
|
||||
case AppxPackage.Info.PackageRole.Resource: return "resource";
|
||||
default:
|
||||
case AppxPackage.Info.PackageRole.Unknown: return "unknown";
|
||||
}
|
||||
}),
|
||||
identity = new {
|
||||
name = id.Name,
|
||||
publisher = id.Publisher,
|
||||
version = id.Version.Expression,
|
||||
realVersion = id.RealVersion.Expression,
|
||||
architecture = id.ProcessArchitecture.Select (e => {
|
||||
switch (e)
|
||||
{
|
||||
case AppxPackage.Info.Architecture.ARM: return "arm";
|
||||
case AppxPackage.Info.Architecture.ARM64: return "arm64";
|
||||
case AppxPackage.Info.Architecture.Neutral: return "neutral";
|
||||
default:
|
||||
case AppxPackage.Info.Architecture.Unknown: return "unknown";
|
||||
case AppxPackage.Info.Architecture.x64: return "x64";
|
||||
case AppxPackage.Info.Architecture.x86: return "x86";
|
||||
}
|
||||
}).ToList (),
|
||||
familyName = id.FamilyName,
|
||||
fullName = id.FullName,
|
||||
resourceId = id.ResourceId
|
||||
},
|
||||
properties = new {
|
||||
displayName = prop.DisplayName,
|
||||
publisherDisplayName = prop.Publisher,
|
||||
description = prop.Description,
|
||||
logo = prop.Logo,
|
||||
framework = prop.Framework,
|
||||
resourcePackage = prop.ResourcePackage
|
||||
},
|
||||
prerequisite = new {
|
||||
osMinVersion = pre.OSMinVersion.ToString (),
|
||||
osMaxVersionTested = pre.OSMaxVersionTested.ToString (),
|
||||
_osMinVersion = pre.OSMinVersionDescription,
|
||||
_osMaxVersionTested = pre.OSMaxVersionDescription
|
||||
},
|
||||
applications = apps.Select (e => {
|
||||
var dict = new Dictionary<string, string> ();
|
||||
foreach (var kv in e)
|
||||
{
|
||||
if (kv.Key.IndexOf ("Base64") >= 0) continue;
|
||||
var value = e.NewAt (kv.Key, pr.EnablePri);
|
||||
if (string.IsNullOrWhiteSpace (value))
|
||||
value = e.At (kv.Key);
|
||||
dict [kv.Key] = value;
|
||||
}
|
||||
return dict;
|
||||
}).ToList (),
|
||||
capabilities = new {
|
||||
capabilities = caps.Capabilities,
|
||||
deviceCapabilities = caps.DeviceCapabilities
|
||||
},
|
||||
dependencies = deps.Select (e => new {
|
||||
name = e.Name,
|
||||
publisher = e.Publisher,
|
||||
minVersion = e.Version.ToString ()
|
||||
})
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从 PackageReader 中获取指定路径的值。
|
||||
/// 支持格式:路径中可使用 '.' 或 ':' 分隔,支持索引器 '[index]',支持 '.length'。
|
||||
/// 大小写不敏感,自动去除首尾空白。
|
||||
/// 示例:
|
||||
/// "Identity" -> 返回 Identity 字典
|
||||
/// "Identity.Name" -> 返回名称
|
||||
/// "Properties.Publisher" -> 返回发布者显示名称(别名)
|
||||
/// "applications[0]" -> 返回第一个应用字典
|
||||
/// "applications.length" -> 返回应用个数
|
||||
/// "Applications[0].LogoBase64" -> 返回第一个应用的 LogoBase64
|
||||
/// </summary>
|
||||
public static object GetItem (this PackageReader pr, string item)
|
||||
{
|
||||
if (pr == null) throw new ArgumentNullException ("pr");
|
||||
if (string.IsNullOrWhiteSpace (item)) return null;
|
||||
|
||||
string path = item.Trim ().Replace (':', '.');
|
||||
object result = ResolvePath (pr, path);
|
||||
return SerializeObject (result, pr, false);
|
||||
}
|
||||
|
||||
private static object ResolvePath (object target, string path)
|
||||
{
|
||||
if (target == null) return null;
|
||||
if (string.IsNullOrEmpty (path)) return target;
|
||||
|
||||
string [] segments = path.Split ('.');
|
||||
object current = target;
|
||||
|
||||
foreach (string seg in segments)
|
||||
{
|
||||
if (current == null) return null;
|
||||
|
||||
if (seg.Contains ("[") && seg.Contains ("]"))
|
||||
{
|
||||
int bracketStart = seg.IndexOf ('[');
|
||||
string propName = seg.Substring (0, bracketStart);
|
||||
string indexStr = seg.Substring (bracketStart + 1, seg.Length - bracketStart - 2);
|
||||
int index;
|
||||
if (!int.TryParse (indexStr, out index))
|
||||
return null;
|
||||
|
||||
object collection = GetPropertyValue (current, propName);
|
||||
if (collection == null) return null;
|
||||
current = GetIndexedValue (collection, index);
|
||||
}
|
||||
else if (string.Equals (seg, "length", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals (seg, "count", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
current = GetLength (current);
|
||||
}
|
||||
else
|
||||
{
|
||||
current = GetPropertyValue (current, seg);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private static object GetPropertyValue (object target, string propName)
|
||||
{
|
||||
if (target == null) return null;
|
||||
Type type = target.GetType ();
|
||||
|
||||
// 根对象 PackageReader 的属性名映射(不区分大小写)
|
||||
if (type == typeof (PackageReader))
|
||||
{
|
||||
string mapped = null;
|
||||
string lower = propName.ToLowerInvariant ();
|
||||
switch (lower)
|
||||
{
|
||||
case "pkgid":
|
||||
case "id":
|
||||
case "identity": mapped = "Identity"; break;
|
||||
case "prop":
|
||||
case "properties": mapped = "Properties"; break;
|
||||
case "prerequistes":
|
||||
case "prerequisite":
|
||||
case "prerequisites": mapped = "Prerequisites"; break;
|
||||
case "res":
|
||||
case "resources": mapped = "Resources"; break;
|
||||
case "apps":
|
||||
case "applications": mapped = "Applications"; break;
|
||||
case "caps":
|
||||
case "capabilities": mapped = "Capabilities"; break;
|
||||
case "deps":
|
||||
case "dependencies": mapped = "Dependencies"; break;
|
||||
case "type": mapped = "Type"; break;
|
||||
case "role": mapped = "Role"; break;
|
||||
case "valid":
|
||||
case "isvalid": mapped = "IsValid"; break;
|
||||
case "filepath": mapped = "FilePath"; break;
|
||||
}
|
||||
if (mapped != null)
|
||||
propName = mapped;
|
||||
}
|
||||
|
||||
// PRProperties 别名:Publisher / PublisherDisplayName 都映射到 Publisher 属性
|
||||
if (type == typeof (PRProperties))
|
||||
{
|
||||
if (string.Equals (propName, "Publisher", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals (propName, "PublisherDisplayName", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
propName = "Publisher";
|
||||
}
|
||||
}
|
||||
|
||||
// PRApplication 中以 Base64 结尾的属性 -> 调用 NewAtBase64
|
||||
if (type == typeof (PRApplication) && propName.EndsWith ("Base64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string baseKey = propName.Substring (0, propName.Length - 6);
|
||||
PRApplication app = (PRApplication)target;
|
||||
MethodInfo method = typeof (PRApplication).GetMethod ("NewAtBase64", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (method != null)
|
||||
{
|
||||
return method.Invoke (app, new object [] { baseKey });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// PRApplication 普通属性访问(字典键,大小写不敏感)
|
||||
if (type == typeof (PRApplication))
|
||||
{
|
||||
PRApplication app = (PRApplication)target;
|
||||
// 直接使用索引器,内部已处理资源解析和大小写不敏感
|
||||
return app [propName];
|
||||
}
|
||||
|
||||
// MRApplication 同理
|
||||
if (type == typeof (MRApplication))
|
||||
{
|
||||
MRApplication app = (MRApplication)target;
|
||||
return app [propName];
|
||||
}
|
||||
|
||||
// 反射属性(不区分大小写)
|
||||
PropertyInfo prop = type.GetProperty (propName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (prop != null)
|
||||
{
|
||||
return prop.GetValue (target, null);
|
||||
}
|
||||
|
||||
// 反射字段
|
||||
FieldInfo field = type.GetField (propName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (field != null)
|
||||
{
|
||||
return field.GetValue (target);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object GetIndexedValue (object collection, int index)
|
||||
{
|
||||
if (collection == null) return null;
|
||||
|
||||
// PRApplications
|
||||
PRApplications apps = collection as PRApplications;
|
||||
if (apps != null)
|
||||
{
|
||||
if (index >= 0 && index < apps.Applications.Count)
|
||||
return apps.Applications [index];
|
||||
return null;
|
||||
}
|
||||
|
||||
// IList
|
||||
IList list = collection as IList;
|
||||
if (list != null && index >= 0 && index < list.Count)
|
||||
return list [index];
|
||||
|
||||
// 索引器属性
|
||||
PropertyInfo indexer = collection.GetType ().GetProperty ("Item", new [] { typeof (int) });
|
||||
if (indexer != null)
|
||||
return indexer.GetValue (collection, new object [] { index });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetLength (object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
PRApplications apps = obj as PRApplications;
|
||||
if (apps != null) return apps.Applications.Count;
|
||||
PRDependencies deps = obj as PRDependencies;
|
||||
if (deps != null) return deps.Dependencies.Count;
|
||||
IList list = obj as IList;
|
||||
if (list != null) return list.Count;
|
||||
ICollection coll = obj as ICollection;
|
||||
if (coll != null) return coll.Count;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object SerializeObject (object obj, PackageReader pr, bool filterBase64)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
Type type = obj.GetType ();
|
||||
if (type.IsPrimitive || obj is string || obj is decimal || obj is Enum)
|
||||
return obj;
|
||||
|
||||
// PRIdentity
|
||||
if (type == typeof (PRIdentity))
|
||||
{
|
||||
PRIdentity id = (PRIdentity)obj;
|
||||
return new {
|
||||
name = id.Name,
|
||||
publisher = id.Publisher,
|
||||
version = id.Version.Expression,
|
||||
realVersion = id.RealVersion.Expression,
|
||||
architecture = id.ProcessArchitecture.Select (e => {
|
||||
switch (e)
|
||||
{
|
||||
case Architecture.ARM: return "arm";
|
||||
case Architecture.ARM64: return "arm64";
|
||||
case Architecture.Neutral: return "neutral";
|
||||
case Architecture.x64: return "x64";
|
||||
case Architecture.x86: return "x86";
|
||||
default: return "unknown";
|
||||
}
|
||||
}).ToList (),
|
||||
familyName = id.FamilyName,
|
||||
fullName = id.FullName,
|
||||
resourceId = id.ResourceId
|
||||
};
|
||||
}
|
||||
|
||||
// PRProperties
|
||||
if (type == typeof (PRProperties))
|
||||
{
|
||||
PRProperties prop = (PRProperties)obj;
|
||||
return new {
|
||||
displayName = prop.DisplayName,
|
||||
publisherDisplayName = prop.Publisher,
|
||||
description = prop.Description,
|
||||
logo = prop.Logo,
|
||||
framework = prop.Framework,
|
||||
resourcePackage = prop.ResourcePackage
|
||||
};
|
||||
}
|
||||
|
||||
// PRPrerequisites
|
||||
if (type == typeof (PRPrerequisites))
|
||||
{
|
||||
PRPrerequisites pre = (PRPrerequisites)obj;
|
||||
return new {
|
||||
osMinVersion = pre.OSMinVersion,
|
||||
osMaxVersionTested = pre.OSMaxVersionTested,
|
||||
_osMinVersion = pre.OSMinVersionDescription,
|
||||
_osMaxVersionTested = pre.OSMaxVersionDescription
|
||||
};
|
||||
}
|
||||
|
||||
// PRCapabilities
|
||||
if (type == typeof (PRCapabilities))
|
||||
{
|
||||
PRCapabilities caps = (PRCapabilities)obj;
|
||||
return new {
|
||||
capabilities = caps.Capabilities,
|
||||
deviceCapabilities = caps.DeviceCapabilities
|
||||
};
|
||||
}
|
||||
|
||||
// PRDependencies
|
||||
if (type == typeof (PRDependencies))
|
||||
{
|
||||
PRDependencies deps = (PRDependencies)obj;
|
||||
return deps.Select (e => new {
|
||||
name = e.Name,
|
||||
publisher = e.Publisher,
|
||||
minVersion = e.Version.ToString ()
|
||||
}).ToList ();
|
||||
}
|
||||
|
||||
// PRApplications (集合)
|
||||
if (type == typeof (PRApplications))
|
||||
{
|
||||
PRApplications apps = (PRApplications)obj;
|
||||
return apps.Select (e => SerializeObject (e, pr, filterBase64)).ToList ();
|
||||
}
|
||||
|
||||
// PRApplication (单个应用)
|
||||
if (type == typeof (PRApplication))
|
||||
{
|
||||
PRApplication app = (PRApplication)obj;
|
||||
var dict = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kv in app)
|
||||
{
|
||||
if (kv.Key.IndexOf ("Base64", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
continue;
|
||||
string value = app.NewAt (kv.Key, pr.EnablePri);
|
||||
if (string.IsNullOrWhiteSpace (value))
|
||||
value = app.At (kv.Key);
|
||||
dict [kv.Key] = value;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
// IDictionary
|
||||
IDictionary dictObj = obj as IDictionary;
|
||||
if (dictObj != null)
|
||||
{
|
||||
var result = new Dictionary<string, object> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (DictionaryEntry entry in dictObj)
|
||||
{
|
||||
string key = entry.Key?.ToString ();
|
||||
if (string.IsNullOrEmpty (key)) continue;
|
||||
if (filterBase64 && key.EndsWith ("_Base64", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
result [key] = SerializeObject (entry.Value, pr, filterBase64);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// IEnumerable (非字符串)
|
||||
if (!(obj is string))
|
||||
{
|
||||
IEnumerable enumerable = obj as IEnumerable;
|
||||
if (enumerable != null)
|
||||
{
|
||||
var list = new List<object> ();
|
||||
foreach (var item in enumerable)
|
||||
list.Add (SerializeObject (item, pr, filterBase64));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// 后备:反射属性
|
||||
var props = type.GetProperties (BindingFlags.Public | BindingFlags.Instance);
|
||||
var propDict = new Dictionary<string, object> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var prop in props)
|
||||
{
|
||||
if (prop.GetIndexParameters ().Length > 0) continue;
|
||||
string propName = prop.Name;
|
||||
if (filterBase64 && propName.EndsWith ("Base64", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
object val = prop.GetValue (obj, null);
|
||||
propDict [propName] = SerializeObject (val, pr, filterBase64);
|
||||
}
|
||||
return propDict;
|
||||
}
|
||||
}
|
||||
public static class ManifestReaderExt
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用于 CLI 输出的 JSON 对象(与 PackageReaderExt.GetJsonObjectForCli 结构一致)。
|
||||
/// </summary>
|
||||
public static object GetJsonObjectForCli (this ManifestReader mr)
|
||||
{
|
||||
var id = mr.Identity;
|
||||
var prop = mr.Properties;
|
||||
var pre = mr.Prerequisites;
|
||||
var apps = mr.Applications;
|
||||
var caps = mr.Capabilities;
|
||||
var deps = mr.Dependencies;
|
||||
var res = mr.Resources;
|
||||
|
||||
dynamic obj = new {
|
||||
valid = mr.IsValid,
|
||||
type = mr.Type.ToOther (e => {
|
||||
switch ((PackageType)e)
|
||||
{
|
||||
case PackageType.Appx: return "appx";
|
||||
case PackageType.Bundle: return "bundle";
|
||||
default:
|
||||
case PackageType.Unknown: return "unknown";
|
||||
}
|
||||
}),
|
||||
role = mr.Role.ToOther (o => {
|
||||
switch ((PackageRole)o)
|
||||
{
|
||||
case PackageRole.Application: return "application";
|
||||
case PackageRole.Framework: return "framework";
|
||||
case PackageRole.Resource: return "resource";
|
||||
default:
|
||||
case PackageRole.Unknown: return "unknown";
|
||||
}
|
||||
}),
|
||||
identity = new {
|
||||
name = id.Name,
|
||||
publisher = id.Publisher,
|
||||
version = id.Version.Expression,
|
||||
architecture = id.ProcessArchitecture.Select (e => {
|
||||
switch (e)
|
||||
{
|
||||
case Architecture.ARM: return "arm";
|
||||
case Architecture.ARM64: return "arm64";
|
||||
case Architecture.Neutral: return "neutral";
|
||||
case Architecture.x64: return "x64";
|
||||
case Architecture.x86: return "x86";
|
||||
default: return "unknown";
|
||||
}
|
||||
}).ToList (),
|
||||
familyName = id.FamilyName,
|
||||
fullName = id.FullName,
|
||||
resourceId = id.ResourceId
|
||||
},
|
||||
properties = new {
|
||||
displayName = prop.DisplayName,
|
||||
publisherDisplayName = prop.Publisher,
|
||||
description = prop.Description,
|
||||
logo = prop.Logo,
|
||||
framework = prop.Framework,
|
||||
resourcePackage = prop.ResourcePackage
|
||||
},
|
||||
prerequisite = new {
|
||||
osMinVersion = pre.OSMinVersion.ToString (),
|
||||
osMaxVersionTested = pre.OSMaxVersionTested.ToString (),
|
||||
_osMinVersion = pre.OSMinVersionDescription,
|
||||
_osMaxVersionTested = pre.OSMaxVersionDescription
|
||||
},
|
||||
resources = new {
|
||||
languages = res.Languages,
|
||||
scales = res.Scales,
|
||||
dxFeatureLevels = res.DXFeatures.Select (d => {
|
||||
switch (d)
|
||||
{
|
||||
case DXFeatureLevel.Level9: return 9;
|
||||
case DXFeatureLevel.Level10: return 10;
|
||||
case DXFeatureLevel.Level11: return 11;
|
||||
case DXFeatureLevel.Level12: return 12;
|
||||
default: return -1;
|
||||
}
|
||||
}).ToList ()
|
||||
},
|
||||
applications = apps.Select (e => {
|
||||
var dict = new Dictionary<string, string> ();
|
||||
foreach (var kv in e)
|
||||
{
|
||||
if (kv.Key.IndexOf ("Base64", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
continue;
|
||||
string value = e.NewAt (kv.Key, mr.EnablePri);
|
||||
if (string.IsNullOrWhiteSpace (value))
|
||||
value = e.At (kv.Key);
|
||||
dict [kv.Key] = value;
|
||||
}
|
||||
return dict;
|
||||
}).ToList (),
|
||||
capabilities = new {
|
||||
capabilities = caps.Capabilities,
|
||||
deviceCapabilities = caps.DeviceCapabilities
|
||||
},
|
||||
dependencies = deps.Select (e => new {
|
||||
name = e.Name,
|
||||
publisher = e.Publisher,
|
||||
minVersion = e.Version.ToString ()
|
||||
})
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 ManifestReader 中获取指定路径的值。
|
||||
/// 支持格式:路径中可使用 '.' 或 ':' 分隔,支持索引器 '[index]',支持 '.length'。
|
||||
/// 大小写不敏感,自动去除首尾空白。
|
||||
/// 示例:
|
||||
/// "Identity" -> 返回 Identity 字典
|
||||
/// "Identity.Name" -> 返回名称
|
||||
/// "Properties.Publisher" -> 返回发布者显示名称(别名)
|
||||
/// "applications[0]" -> 返回第一个应用字典
|
||||
/// "applications.length" -> 返回应用个数
|
||||
/// "Applications[0].LogoBase64" -> 返回第一个应用的 LogoBase64
|
||||
/// </summary>
|
||||
public static object GetItem (this ManifestReader mr, string item)
|
||||
{
|
||||
if (mr == null) throw new ArgumentNullException ("mr");
|
||||
if (string.IsNullOrWhiteSpace (item)) return null;
|
||||
|
||||
string path = item.Trim ().Replace (':', '.');
|
||||
object result = ResolvePath (mr, path);
|
||||
return SerializeObject (result, mr, false);
|
||||
}
|
||||
|
||||
private static object ResolvePath (object target, string path)
|
||||
{
|
||||
if (target == null) return null;
|
||||
if (string.IsNullOrEmpty (path)) return target;
|
||||
|
||||
string [] segments = path.Split ('.');
|
||||
object current = target;
|
||||
|
||||
foreach (string seg in segments)
|
||||
{
|
||||
if (current == null) return null;
|
||||
|
||||
if (seg.Contains ("[") && seg.Contains ("]"))
|
||||
{
|
||||
int bracketStart = seg.IndexOf ('[');
|
||||
string propName = seg.Substring (0, bracketStart);
|
||||
string indexStr = seg.Substring (bracketStart + 1, seg.Length - bracketStart - 2);
|
||||
int index;
|
||||
if (!int.TryParse (indexStr, out index))
|
||||
return null;
|
||||
|
||||
object collection = GetPropertyValue (current, propName);
|
||||
if (collection == null) return null;
|
||||
current = GetIndexedValue (collection, index);
|
||||
}
|
||||
else if (string.Equals (seg, "length", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals (seg, "count", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
current = GetLength (current);
|
||||
}
|
||||
else
|
||||
{
|
||||
current = GetPropertyValue (current, seg);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private static object GetPropertyValue (object target, string propName)
|
||||
{
|
||||
if (target == null) return null;
|
||||
Type type = target.GetType ();
|
||||
|
||||
// 根对象 ManifestReader 的属性名映射(不区分大小写)
|
||||
if (type == typeof (ManifestReader))
|
||||
{
|
||||
string mapped = null;
|
||||
string lower = propName.ToLowerInvariant ();
|
||||
switch (lower)
|
||||
{
|
||||
case "pkgid":
|
||||
case "id":
|
||||
case "identity": mapped = "Identity"; break;
|
||||
case "prop":
|
||||
case "properties": mapped = "Properties"; break;
|
||||
case "prerequistes":
|
||||
case "prerequisite":
|
||||
case "prerequisites": mapped = "Prerequisites"; break;
|
||||
case "res":
|
||||
case "resources": mapped = "Resources"; break;
|
||||
case "apps":
|
||||
case "applications": mapped = "Applications"; break;
|
||||
case "caps":
|
||||
case "capabilities": mapped = "Capabilities"; break;
|
||||
case "deps":
|
||||
case "dependencies": mapped = "Dependencies"; break;
|
||||
case "type": mapped = "Type"; break;
|
||||
case "role": mapped = "Role"; break;
|
||||
case "valid":
|
||||
case "isvalid": mapped = "IsValid"; break;
|
||||
case "file":
|
||||
case "filepath": mapped = "FilePath"; break;
|
||||
case "fileroot": mapped = "FileRoot"; break;
|
||||
}
|
||||
if (mapped != null)
|
||||
propName = mapped;
|
||||
}
|
||||
|
||||
// MRProperties 别名:Publisher / PublisherDisplayName 都映射到 Publisher 属性
|
||||
if (type == typeof (MRProperties))
|
||||
{
|
||||
if (string.Equals (propName, "Publisher", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals (propName, "PublisherDisplayName", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
propName = "Publisher";
|
||||
}
|
||||
}
|
||||
|
||||
// MRApplication 中以 Base64 结尾的属性 -> 调用 NewAtBase64
|
||||
if (type == typeof (MRApplication) && propName.EndsWith ("Base64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string baseKey = propName.Substring (0, propName.Length - 6);
|
||||
MRApplication app = (MRApplication)target;
|
||||
MethodInfo method = typeof (MRApplication).GetMethod ("NewAtBase64", BindingFlags.Public | BindingFlags.Instance);
|
||||
if (method != null)
|
||||
{
|
||||
return method.Invoke (app, new object [] { baseKey });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// MRApplication 普通属性访问(字典键,大小写不敏感)
|
||||
if (type == typeof (MRApplication))
|
||||
{
|
||||
MRApplication app = (MRApplication)target;
|
||||
return app [propName];
|
||||
}
|
||||
|
||||
// 反射属性(不区分大小写)
|
||||
PropertyInfo prop = type.GetProperty (propName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (prop != null)
|
||||
{
|
||||
return prop.GetValue (target, null);
|
||||
}
|
||||
|
||||
// 反射字段
|
||||
FieldInfo field = type.GetField (propName,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
if (field != null)
|
||||
{
|
||||
return field.GetValue (target);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object GetIndexedValue (object collection, int index)
|
||||
{
|
||||
if (collection == null) return null;
|
||||
|
||||
MRApplications apps = collection as MRApplications;
|
||||
if (apps != null)
|
||||
{
|
||||
if (index >= 0 && index < apps.Applications.Count)
|
||||
return apps.Applications [index];
|
||||
return null;
|
||||
}
|
||||
|
||||
IList list = collection as IList;
|
||||
if (list != null && index >= 0 && index < list.Count)
|
||||
return list [index];
|
||||
|
||||
PropertyInfo indexer = collection.GetType ().GetProperty ("Item", new [] { typeof (int) });
|
||||
if (indexer != null)
|
||||
return indexer.GetValue (collection, new object [] { index });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetLength (object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
MRApplications apps = obj as MRApplications;
|
||||
if (apps != null) return apps.Applications.Count;
|
||||
MRDependencies deps = obj as MRDependencies;
|
||||
if (deps != null) return deps.Dependencies.Count;
|
||||
IList list = obj as IList;
|
||||
if (list != null) return list.Count;
|
||||
ICollection coll = obj as ICollection;
|
||||
if (coll != null) return coll.Count;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object SerializeObject (object obj, ManifestReader mr, bool filterBase64)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
Type type = obj.GetType ();
|
||||
if (type.IsPrimitive || obj is string || obj is decimal || obj is Enum)
|
||||
return obj;
|
||||
|
||||
// MRIdentity
|
||||
if (type == typeof (MRIdentity))
|
||||
{
|
||||
MRIdentity id = (MRIdentity)obj;
|
||||
return new {
|
||||
name = id.Name,
|
||||
publisher = id.Publisher,
|
||||
version = id.Version.Expression,
|
||||
architecture = id.ProcessArchitecture.Select (e => {
|
||||
switch (e)
|
||||
{
|
||||
case Architecture.ARM: return "arm";
|
||||
case Architecture.ARM64: return "arm64";
|
||||
case Architecture.Neutral: return "neutral";
|
||||
case Architecture.x64: return "x64";
|
||||
case Architecture.x86: return "x86";
|
||||
default: return "unknown";
|
||||
}
|
||||
}).ToList (),
|
||||
familyName = id.FamilyName,
|
||||
fullName = id.FullName,
|
||||
resourceId = id.ResourceId
|
||||
};
|
||||
}
|
||||
|
||||
// MRProperties
|
||||
if (type == typeof (MRProperties))
|
||||
{
|
||||
MRProperties prop = (MRProperties)obj;
|
||||
return new {
|
||||
displayName = prop.DisplayName,
|
||||
publisherDisplayName = prop.Publisher,
|
||||
description = prop.Description,
|
||||
logo = prop.Logo,
|
||||
framework = prop.Framework,
|
||||
resourcePackage = prop.ResourcePackage
|
||||
};
|
||||
}
|
||||
|
||||
// MRPrerequisites
|
||||
if (type == typeof (MRPrerequisites))
|
||||
{
|
||||
MRPrerequisites pre = (MRPrerequisites)obj;
|
||||
return new {
|
||||
osMinVersion = pre.OSMinVersion,
|
||||
osMaxVersionTested = pre.OSMaxVersionTested,
|
||||
_osMinVersion = pre.OSMinVersionDescription,
|
||||
_osMaxVersionTested = pre.OSMaxVersionDescription
|
||||
};
|
||||
}
|
||||
|
||||
// MRResources
|
||||
if (type == typeof (MRResources))
|
||||
{
|
||||
MRResources res = (MRResources)obj;
|
||||
return new {
|
||||
languages = res.Languages,
|
||||
scales = res.Scales,
|
||||
dxFeatureLevels = res.DXFeatures.Select (d => {
|
||||
switch (d)
|
||||
{
|
||||
case DXFeatureLevel.Level9: return 9;
|
||||
case DXFeatureLevel.Level10: return 10;
|
||||
case DXFeatureLevel.Level11: return 11;
|
||||
case DXFeatureLevel.Level12: return 12;
|
||||
default: return -1;
|
||||
}
|
||||
}).ToList ()
|
||||
};
|
||||
}
|
||||
|
||||
// MRCapabilities
|
||||
if (type == typeof (MRCapabilities))
|
||||
{
|
||||
MRCapabilities caps = (MRCapabilities)obj;
|
||||
return new {
|
||||
capabilities = caps.Capabilities,
|
||||
deviceCapabilities = caps.DeviceCapabilities
|
||||
};
|
||||
}
|
||||
|
||||
// MRDependencies
|
||||
if (type == typeof (MRDependencies))
|
||||
{
|
||||
MRDependencies deps = (MRDependencies)obj;
|
||||
return deps.Select (e => new {
|
||||
name = e.Name,
|
||||
publisher = e.Publisher,
|
||||
minVersion = e.Version.ToString ()
|
||||
}).ToList ();
|
||||
}
|
||||
|
||||
// MRApplications (集合)
|
||||
if (type == typeof (MRApplications))
|
||||
{
|
||||
MRApplications apps = (MRApplications)obj;
|
||||
return apps.Select (e => SerializeObject (e, mr, filterBase64)).ToList ();
|
||||
}
|
||||
|
||||
// MRApplication (单个应用)
|
||||
if (type == typeof (MRApplication))
|
||||
{
|
||||
MRApplication app = (MRApplication)obj;
|
||||
var dict = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kv in app)
|
||||
{
|
||||
if (kv.Key.IndexOf ("Base64", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
continue;
|
||||
string value = app.NewAt (kv.Key, mr.EnablePri);
|
||||
if (string.IsNullOrWhiteSpace (value))
|
||||
value = app.At (kv.Key);
|
||||
dict [kv.Key] = value;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
// IDictionary
|
||||
IDictionary dictObj = obj as IDictionary;
|
||||
if (dictObj != null)
|
||||
{
|
||||
var result = new Dictionary<string, object> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (DictionaryEntry entry in dictObj)
|
||||
{
|
||||
string key = entry.Key?.ToString ();
|
||||
if (string.IsNullOrEmpty (key)) continue;
|
||||
if (filterBase64 && key.EndsWith ("_Base64", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
result [key] = SerializeObject (entry.Value, mr, filterBase64);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// IEnumerable (非字符串)
|
||||
if (!(obj is string))
|
||||
{
|
||||
IEnumerable enumerable = obj as IEnumerable;
|
||||
if (enumerable != null)
|
||||
{
|
||||
var list = new List<object> ();
|
||||
foreach (var item in enumerable)
|
||||
list.Add (SerializeObject (item, mr, filterBase64));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
// 后备:反射属性
|
||||
var props = type.GetProperties (BindingFlags.Public | BindingFlags.Instance);
|
||||
var propDict = new Dictionary<string, object> (StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var prop in props)
|
||||
{
|
||||
if (prop.GetIndexParameters ().Length > 0) continue;
|
||||
string propName = prop.Name;
|
||||
if (filterBase64 && propName.EndsWith ("Base64", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
object val = prop.GetValue (obj, null);
|
||||
propDict [propName] = SerializeObject (val, mr, filterBase64);
|
||||
}
|
||||
return propDict;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,991 @@
|
||||
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).
|
||||
/? 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)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.InputEncoding = Encoding.UTF8;
|
||||
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")
|
||||
});
|
||||
RefreshConfig ();
|
||||
var cmds = parser.Parse (args);
|
||||
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>...]
|
||||
|
||||
Operation:
|
||||
Remove (uninstall) 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
|
||||
|
||||
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
|
||||
");
|
||||
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);
|
||||
}
|
||||
}
|
||||
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"))
|
||||
{
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
112
PkgCLI/Text.cs
Normal file
112
PkgCLI/Text.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace System
|
||||
{
|
||||
namespace Text
|
||||
{
|
||||
public static class Escape
|
||||
{
|
||||
/// <summary>按 JSON 规范对字符串进行转义。</summary>
|
||||
/// <param name="input">原始字符串。</param>
|
||||
/// <returns>转义后的 JSON 字符串字面量内容(不含外围双引号)。</returns>
|
||||
public static string ToEscape (string input)
|
||||
{
|
||||
if (input == null) throw new ArgumentNullException (nameof (input));
|
||||
if (input.Length == 0) return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder (input.Length);
|
||||
foreach (char c in input)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': sb.Append ("\\\""); break;
|
||||
case '\\': sb.Append ("\\\\"); break;
|
||||
case '\b': sb.Append ("\\b"); break;
|
||||
case '\f': sb.Append ("\\f"); break;
|
||||
case '\n': sb.Append ("\\n"); break;
|
||||
case '\r': sb.Append ("\\r"); break;
|
||||
case '\t': sb.Append ("\\t"); break;
|
||||
default:
|
||||
// 控制字符 (U+0000 - U+001F) 需转义为 \uXXXX
|
||||
if (c <= 0x1F)
|
||||
{
|
||||
sb.Append ("\\u");
|
||||
sb.Append (((int)c).ToString ("X4"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append (c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
/// <summary>按 JSON 规范反转义字符串。</summary>
|
||||
/// <param name="input">转义后的 JSON 字符串内容(不含外围双引号)。</param>
|
||||
/// <returns>原始字符串。</returns>
|
||||
/// <exception cref="FormatException">遇到非法转义序列时抛出。</exception>
|
||||
public static string Unescape (string input)
|
||||
{
|
||||
if (input == null) throw new ArgumentNullException (nameof (input));
|
||||
if (input.Length == 0) return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder (input.Length);
|
||||
int i = 0;
|
||||
while (i < input.Length)
|
||||
{
|
||||
char c = input [i];
|
||||
if (c == '\\')
|
||||
{
|
||||
i++;
|
||||
if (i >= input.Length)
|
||||
throw new FormatException ("字符串末尾包含不完整的转义序列。");
|
||||
|
||||
char next = input [i];
|
||||
switch (next)
|
||||
{
|
||||
case '"': sb.Append ('"'); break;
|
||||
case '\\': sb.Append ('\\'); break;
|
||||
case '/': sb.Append ('/'); break; // 允许转义斜杠
|
||||
case 'b': sb.Append ('\b'); break;
|
||||
case 'f': sb.Append ('\f'); break;
|
||||
case 'n': sb.Append ('\n'); break;
|
||||
case 'r': sb.Append ('\r'); break;
|
||||
case 't': sb.Append ('\t'); break;
|
||||
case 'u':
|
||||
i++;
|
||||
if (i + 4 > input.Length)
|
||||
throw new FormatException ("\\u 转义后必须跟随 4 位十六进制数字。");
|
||||
|
||||
string hex = input.Substring (i, 4);
|
||||
int codePoint; // 先声明变量,兼容 C# 5/6
|
||||
if (!int.TryParse (hex,
|
||||
NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture,
|
||||
out codePoint))
|
||||
{
|
||||
throw new FormatException (string.Format ("无效的 Unicode 转义序列: \\u{0}", hex));
|
||||
}
|
||||
|
||||
sb.Append ((char)codePoint);
|
||||
i += 3; // 循环末尾会再加1,因此这里只增加3
|
||||
break;
|
||||
default:
|
||||
throw new FormatException (string.Format ("未识别的转义序列: \\{0}", next));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append (c);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return sb.ToString ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
PkgCLI/packages.config
Normal file
4
PkgCLI/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -381,6 +381,7 @@ void DestroyPriFileInstance (PCSPRIFILE pFilePri)
|
||||
if (!pFilePri) return;
|
||||
try
|
||||
{
|
||||
#ifdef ELDER_FUNC
|
||||
CreateScopedLock (g_threadlock);
|
||||
auto it = g_tasklist.find (pFilePri);
|
||||
if (it != g_tasklist.end ())
|
||||
@@ -388,6 +389,7 @@ void DestroyPriFileInstance (PCSPRIFILE pFilePri)
|
||||
it->second.bIsRunning = false;
|
||||
g_tasklist.erase (it);
|
||||
}
|
||||
#endif
|
||||
IntPtr handlePtr = IntPtr (pFilePri);
|
||||
System::Runtime::InteropServices::GCHandle handle = System::Runtime::InteropServices::GCHandle::FromIntPtr (handlePtr);
|
||||
PriFileInst ^inst = safe_cast <PriFileInst ^> (handle.Target);
|
||||
|
||||
Binary file not shown.
@@ -442,6 +442,8 @@
|
||||
createShortcutButton.setAttribute("data-app-user-model-id", item.AppUserModelID);
|
||||
createShortcutButton.textContent = strres.get("MANAGER_APP_SHORTCUTCREATE_TITLE");
|
||||
createShortcutButton.style.marginRight = "10px";
|
||||
appItem.setAttribute("title", item.Id);
|
||||
appItem.setAttribute("aria-label", item.DisplayName || item.ShortName);
|
||||
Windows.UI.Event.Util.addEvent(launchButton, "click", function(e) {
|
||||
e.stopPropagation();
|
||||
Package.manager.active(this.getAttribute("data-app-user-model-id"));
|
||||
|
||||
Reference in New Issue
Block a user