# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/.
r"""Repackage ZIP archives (or directories) into MSIX App Packages.
# Known issues
- The icons in the Start Menu have a solid colour tile behind them. I think
this is an issue with plating. """
import functools import itertools import logging import os import re import shutil import subprocess import sys import time import urllib from collections import defaultdict from pathlib import Path
import mozpack.path as mozpath from mach.util import get_state_dir from mozfile import which from mozpack.copier import FileCopier from mozpack.files import FileFinder, JarFinder from mozpack.manifests import InstallManifest from mozpack.mozjar import JarReader from mozpack.packager.unpack import UnpackFinder from six.moves import shlex_quote
from mozbuild.dirutils import ensureParentDir from mozbuild.repackaging.application_ini import get_application_ini_values
def get_embedded_version(version, buildid):
r"""Turn a display version into "dotted quad" notation.
N.b.: some parts of the MSIX packaging ecosystem require the final part of
the dotted quad to be identically 0, so we enforce that here. """
# It's irritating to roll our own version parsing, but the tree doesn't seem # to contain exactly what we need at this time.
version = version.rsplit("esr", 1)[0]
alpha = "a"in version
tail = None if"a"in version:
head, tail = version.rsplit("a", 1) if tail != "1": # Disallow anything beyond `X.Ya1`. raise ValueError(
f"Alpha version not of the form X.0a1 is not supported: {version}"
)
tail = buildid elif"b"in version:
head, tail = version.rsplit("b", 1) if len(head.split(".")) > 2: raise ValueError(
f"Beta version not of the form X.YbZ is not supported: {version}"
) elif"rc"in version:
head, tail = version.rsplit("rc", 1) if len(head.split(".")) > 2: raise ValueError(
f"Release candidate version not of the form X.YrcZ is not supported: {version}"
) else:
head = version
if alpha: # Nightly builds are all `X.0a1`, which isn't helpful. Include build ID # to disambiguate. But each part of the dotted quad is 16 bits, so we # have to squash. if components[1] != "0": # Disallow anything beyond `X.0a1`. raise ValueError(
f"Alpha version not of the form X.0a1 is not supported: {version}"
)
# Last two digits only to save space. Nightly builds in 2066 and 2099 # will be impacted, but future us can deal with that.
year = buildid[2:4] if year[0] == "0": # Avoid leading zero, like `.0YMm`.
year = year[1:]
month = buildid[4:6]
day = buildid[6:8] if day[0] == "0": # Avoid leading zero, like `.0DHh`.
day = day[1:]
hour = buildid[8:10]
def remove_single_line_comments(text): """Remove C++ style single-line comments from the text."""
lines = [] for line in text.splitlines():
line = re.sub("//.*", "", line)
lines.append(line)
return"\n".join(lines)
def get_appconstants_sys_mjs_values(finder, *args):
r"""Extract values, such as the display version like `MOZ_APP_VERSION_DISPLAY: "...";`, from the omnijar. This allows to determine the beta number, like
`X.YbW`, where the regular beta version is only `X.Y`. Takes a list of
names and returns an iterator of the unique such value found for each name.
Raises an exception if a name isnot found orif multiple values are found. """
lines = defaultdict(list) for _, f in finder.find("**/modules/AppConstants.sys.mjs"): # MOZ_OFFICIAL_BRANDING is split across multiple lines and there is a # comment in between property key and value. Remove the comment, line # breaks and spaces immediately following ":"s so those values can be # read.
data = remove_single_line_comments(f.open().read().decode("utf-8"))
data = re.sub(":[\n\\s]+", ":", data) for line in data.splitlines(): for arg in args: if arg in line:
lines[arg].append(line)
for arg in args:
(value,) = lines[arg] # We expect exactly one definition.
_, _, value = value.partition(":")
value = value.strip().strip('",;') yield value
def get_branding(use_official, topsrcdir, build_app, finder, log=None): """Figure out which branding directory to use."""
conf_vars = mozpath.join(topsrcdir, build_app, "confvars.sh")
def conf_vars_value(key):
lines = [line.strip() for line in open(conf_vars).readlines()] for line in lines: if line and line[0] == "#": continue if key notin line: continue
_, _, value = line.partition("=") ifnot value: continue
log(
logging.INFO, "msix",
{"key": key, "conf_vars": conf_vars, "value": value}, "Read '{key}' from {conf_vars}: {value}",
) return value
log(
logging.ERROR, "msix",
{"key": key, "conf_vars": conf_vars}, "Unable to find '{key}' in {conf_vars}!",
)
if use_official: # Read MOZ_OFFICIAL_BRANDING_DIRECTORY from confvars.sh
branding_reason = "'MOZ_OFFICIAL_BRANDING' set"
branding = conf_vars_value("MOZ_OFFICIAL_BRANDING_DIRECTORY") else: # Check if --with-branding was used when building
log(
logging.INFO, "msix",
{}, "Checking buildconfig.html for --with-branding build flag.",
) for _, f in finder.find("**/chrome/toolkit/content/global/buildconfig.html"):
data = f.open().read().decode("utf-8")
match = re.search(r"--with-branding=([a-z/]+)", data) if match:
branding_reason = "'--with-branding' set"
branding = match.group(1)
log(
logging.INFO, "msix",
{ "branding_reason": branding_reason, "branding": branding,
}, "{branding_reason}; Using branding from '{branding}'.",
) return mozpath.join(topsrcdir, branding)
def unpack_msix(input_msix, output, log=None, verbose=False):
r"""Unpack the given MSIX to the given output directory.
MSIX packages are ZIP files, but they are Zip64/version 4.5 ZIP files, so
`mozjar.py` doesn't yet handle. Unpack using `unzip{.exe}` for simplicity.
In addition, file names inside the MSIX package are URL quoted. URL unquote
here. """
# Sanity check: is this an MSIX?
temp_finder = FileFinder(output) ifnot temp_finder.contains("AppxManifest.xml"): raise ValueError("MSIX file does not contain 'AppxManifest.xml'?")
# Files in the MSIX are URL encoded/quoted; unquote here. for dirpath, dirs, files in os.walk(output): # This is a one way to update (in place, for os.walk) the variable `dirs` while iterating # over it and `files`. for i, (p, var) in itertools.chain(
enumerate((f, files) for f in files), enumerate((g, dirs) for g in dirs)
):
q = urllib.parse.unquote(p) if p != q:
log(
logging.DEBUG, "msix",
{ "dirpath": dirpath, "p": p, "q": q,
}, "URL unquoting '{p}' -> '{q}' in {dirpath}",
)
# The "package root" of our MSIX packages is like "Mozilla Firefox Beta Package Root", i.e., it # varies by channel. This is an easy way to determine it. for p, _ in temp_finder.find("**/application.ini"):
relpath = os.path.split(p)[0]
# The application executable, like `firefox.exe`, is in this directory. return mozpath.normpath(mozpath.join(output, relpath))
# TODO: maybe we can fish this from the package directly? Maybe from a DLL, # maybe from application.ini? if arch isNoneor arch notin _MSIX_ARCH.keys(): raise Exception( "arch name must be provided and one of {}.".format(_MSIX_ARCH.keys())
)
ifnot os.path.exists(dir_or_package): raise Exception("{} does not exist".format(dir_or_package))
if (
os.path.isfile(dir_or_package) and os.path.splitext(dir_or_package)[1] == ".msix"
): # The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
msix_dir = mozpath.normsep(
mozpath.join(
get_state_dir(), "cache", "mach-msix", "msix-unpack",
)
)
if os.path.exists(msix_dir):
shutil.rmtree(msix_dir)
ensureParentDir(msix_dir)
first = next(values) ifnot displayname:
displayname = "Mozilla {}".format(first)
if channel == "beta": # Release (official) and Beta share branding. Differentiate Beta a little bit.
displayname += " Beta"
second = next(values)
vendor = vendor or second
# For `AppConstants.sys.mjs` and `brand.properties`, which are in the omnijar in packaged # builds. The nested langpack XPI files can't be read by `mozjar.py`.
unpack_finder = UnpackFinder(finder, unpack_xpi=False)
values = get_appconstants_sys_mjs_values(
unpack_finder, "MOZ_OFFICIAL_BRANDING", "MOZ_BUILD_APP", "MOZ_APP_NAME", "MOZ_APP_VERSION_DISPLAY", "MOZ_BUILDID",
) try:
use_official_branding = {"true": True, "false": False}[next(values)] except KeyError as err: raise Exception(
f"Unexpected value '{err.args[0]}' found for 'MOZ_OFFICIAL_BRANDING'."
) fromNone
ifnot version:
display_version = next(values)
buildid = next(values)
version = get_embedded_version(display_version, buildid)
log(
logging.INFO, "msix",
{ "version": version, "display_version": display_version, "buildid": buildid,
}, "AppConstants.sys.mjs display version is '{display_version}' and build ID is"
+ " '{buildid}': embedded version will be '{version}'",
)
# TODO: Bug 1721922: localize this description via Fluent.
lines = [] for _, f in unpack_finder.find("**/chrome/en-US/locale/branding/brand.properties"):
lines.extend(
line for line in f.open().read().decode("utf-8").splitlines() if"brandFullName"in line
)
(brandFullName,) = lines # We expect exactly one definition.
_, _, brandFullName = brandFullName.partition("=")
brandFullName = brandFullName.strip()
if channel == "beta": # Release (official) and Beta share branding. Differentiate Beta a little bit.
brandFullName += " Beta"
branding = get_branding(
use_official_branding, topsrcdir, build_app, unpack_finder, log
) ifnot os.path.isdir(branding): raise Exception("branding dir {} does not exist".format(branding))
# Discard everything after a '#' comment character.
locale_allowlist = set(
locale.partition("#")[0].strip().lower() for locale in open(os.path.join(template, "msix-all-locales")).readlines() if locale.partition("#")[0].strip()
)
# The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
output_dir = mozpath.normsep(
mozpath.join(
get_state_dir(), "cache", "mach-msix", "msix-temp-{}".format(channel)
)
)
# Like 'Firefox Package Root', 'Firefox Nightly Package Root', 'Firefox Beta # Package Root'. This is `BrandFullName` in the installer, and we want to # be close but to not match. By not matching, we hope to prevent confusion # and/or errors between regularly installed builds and App Package builds.
instdir = "{} Package Root".format(displayname)
# The standard package name is like "CompanyNoSpaces.ProductNoSpaces".
identity = identity or"{}.{}".format(vendor, displayname).replace(" ", "")
# We might want to include the publisher ID hash here. I.e., # "__{publisherID}". My locally produced MSIX was named like # `Mozilla.MozillaFirefoxNightly_89.0.0.0_x64__4gf61r4q480j0`, suggesting also a # missing field, but it's not necessary, since this is just an output file name.
package_output_name = "{identity}_{version}_{arch}".format(
identity=identity, version=version, arch=_MSIX_ARCH[arch]
) # The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
default_output = mozpath.normsep(
mozpath.join(
get_state_dir(), "cache", "mach-msix", "{}.msix".format(package_output_name)
)
)
output = output or default_output
log(logging.INFO, "msix", {"output": output}, "Repackaging to: {output}")
m = InstallManifest()
m.add_copy(mozpath.join(template, "Resources.pri"), "Resources.pri")
# TODO: Bug 1710147: filter out MSVCRT files and use a dependency instead. for p, f in finder: ifnot os.path.isdir(dir_or_package): # In archived builds, `p` is like "firefox/firefox.exe"; we want just "firefox.exe".
pp = os.path.relpath(p, app_name) else: # In local builds and unpacked MSIX directories, `p` is like "firefox.exe" already.
pp = p
if pp.startswith("distribution"): # Treat any existing distribution as a distribution directory, # potentially with language packs. This makes it easy to repack # unpacked MSIXes.
distribution_dir = mozpath.join(dir_or_package, "distribution") if distribution_dir notin distribution_dirs:
distribution_dirs.append(distribution_dir)
# Locales to declare as supported in `AppxManifest.xml`.
locales = set(["en-US"])
for distribution_dir in [
mozpath.join(template, "distribution")
] + distribution_dirs:
log(
logging.INFO, "msix",
{"dir": distribution_dir}, "Adding distribution files from {dir}",
)
# In automation, we have no easy way to remap the names of artifacts fetched from dependent # tasks. In particular, langpacks will be named like `target.langpack.xpi`. The fetch # tasks do allow us to put them in a per-locale directory, so that the entire set can be # fetched. Here we remap the names.
finder = FileFinder(distribution_dir)
for p, f in finder:
locale = None if os.path.basename(p) == "target.langpack.xpi": # Turn "/path/to/LOCALE/target.langpack.xpi" into "LOCALE". This is how langpacks # are presented in CI.
base, locale = os.path.split(os.path.dirname(p))
# Like "locale-LOCALE/langpack-LOCALE@firefox.mozilla.org.xpi". This is what AMO # serves and how flatpak builds name langpacks, but not how snap builds name # langpacks. I can't explain the discrepancy.
dest = mozpath.normsep(
mozpath.join(
base,
f"locale-{locale}",
f"langpack-{locale}@{app_name}.mozilla.org.xpi",
)
)
elif os.path.basename(p).startswith("langpack-"): # Turn "/path/to/langpack-LOCALE@firefox.mozilla.org.xpi" into "LOCALE". This is # how langpacks are presented from an unpacked MSIX.
_, _, locale = os.path.basename(p).partition("langpack-")
locale, _, _ = locale.partition("@")
dest = p
else:
dest = p
if locale:
locale = locale.strip().lower()
locales.add(locale)
log(
logging.DEBUG, "msix",
{"locale": locale, "dest": dest}, "Distributing locale '{locale}' from {dest}",
)
dest = mozpath.normsep(
mozpath.join("VFS", "ProgramFiles", instdir, "distribution", dest)
) if copier.contains(dest):
log(
logging.INFO, "msix",
{"dest": dest, "path": mozpath.join(finder.base, p)}, "Skipping duplicate: {dest} from {path}",
) continue
log(
logging.DEBUG, "msix",
{"dest": dest, "path": mozpath.join(finder.base, p)}, "Adding distribution path: {dest} from {path}",
)
copier.add(
dest,
f,
)
locales.remove("en-US")
# Windows MSIX packages support a finite set of locales: see # https://docs.microsoft.com/en-us/windows/uwp/publish/supported-languages, which is encoded in # https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/msix-all-locales. # We distribute all of the langpacks supported by the release channel in our MSIX, which is # encoded in https://searchfox.org/mozilla-central/source/browser/locales/all-locales. But we # only advertise support in the App manifest for the intersection of that set and the set of # supported locales. # # We distribute all langpacks to avoid the following issue. Suppose a user manually installs a # langpack that is not supported by Windows, and then updates the installed MSIX package. MSIX # package upgrades are essentially paveover installs, so there is no opportunity for Firefox to # update the langpack before the update. But, since all langpacks are bundled with the MSIX, # that langpack will be up-to-date, preventing one class of YSOD.
unadvertised = set() if locale_allowlist:
unadvertised = locales - locale_allowlist
locales = locales & locale_allowlist for locale in sorted(unadvertised):
log(
logging.INFO, "msix",
{"locale": locale}, "Not advertising distributed locale '{locale}' that is not recognized by Windows",
)
locales = ["en-US"] + list(sorted(locales))
resource_language_list = "\n".join(
f' <Resource Language="{locale}" />'for locale in locales
)
m.add_preprocess(
mozpath.join(template, "AppxManifest.xml.in"), "AppxManifest.xml",
[],
defines=defines,
marker="<!-- #", # So that we can have well-formed XML.
)
m.populate_registry(copier)
start = time.monotonic()
result = copier.copy(
output_dir, remove_empty_directories=True, skip_if_older=not force
) if log:
log_copy_result(log, time.monotonic() - start, output_dir, result)
if verbose: # Dump AppxManifest.xml contents for ease of debugging.
log(logging.DEBUG, "msix", {}, "AppxManifest.xml")
log(logging.DEBUG, "msix", {}, ">>>") for line in open(mozpath.join(output_dir, "AppxManifest.xml")).readlines():
log(logging.DEBUG, "msix", {}, line[:-1]) # Drop trailing line terminator.
log(logging.DEBUG, "msix", {}, "<<<")
ifnot makeappx:
makeappx = find_sdk_tool("makeappx.exe", log=log) ifnot makeappx: raise ValueError( "makeappx is required; ""set MAKEAPPX or WINDOWSSDKDIR or PATH"
)
# `makeappx.exe` supports both slash and hyphen style arguments; `makemsix` # supports only hyphen style. `makeappx.exe` allows to overwrite and to # provide more feedback, so we prefer invoking with these flags. This will # also accommodate `wine makeappx.exe`.
stdout = subprocess.run(
[makeappx], check=False, capture_output=True, universal_newlines=True
).stdout
is_makeappx = "MakeAppx Tool"in stdout
if is_makeappx:
args = [makeappx, "pack", "/d", output_dir, "/p", output, "/overwrite"] else:
args = [makeappx, "pack", "-d", output_dir, "-p", output] if verbose and is_makeappx:
args.append("/verbose")
joined = " ".join(shlex_quote(arg) for arg in args)
log(logging.INFO, "msix", {"args": args, "joined": joined}, "Invoking: {joined}")
sys.stdout.flush() # Otherwise the subprocess output can be interleaved. if verbose:
subprocess.check_call(args, universal_newlines=True) else: # Suppress output unless we fail. try:
subprocess.check_output(args, universal_newlines=True) except subprocess.CalledProcessError as e:
sys.stderr.write(e.output) raise
return output
def _sign_msix_win(output, force, log, verbose):
powershell_exe = find_sdk_tool("powershell.exe", log=log) ifnot powershell_exe: raise ValueError("powershell is required; ""set POWERSHELL or PATH")
def powershell(argstring, check=True): "Invoke `powershell.exe`. Arguments are given as a string to allow consumer to quote."
args = [powershell_exe, "-c", argstring]
joined = " ".join(shlex_quote(arg) for arg in args)
log(
logging.INFO, "msix", {"args": args, "joined": joined}, "Invoking: {joined}"
) return subprocess.run(
args, check=check, universal_newlines=True, capture_output=True
).stdout
signtool = find_sdk_tool("signtool.exe", log=log) ifnot signtool: raise ValueError( "signtool is required; ""set SIGNTOOL or WINDOWSSDKDIR or PATH"
)
# Our first order of business is to find, or generate, a (self-signed) # certificate.
# These are baked into enough places under `browser/` that we need not # extract constants.
vendor = "Mozilla"
publisher = "CN=Mozilla Corporation, OU=MSIX Packaging"
friendly_name = "Mozilla Corporation MSIX Packaging Test Certificate"
# The convention is $MOZBUILD_STATE_PATH/cache/$FEATURE.
crt_path = mozpath.join(
get_state_dir(), "cache", "mach-msix", "{}.crt".format(friendly_name).replace(" ", "_").lower(),
)
crt_path = mozpath.abspath(crt_path)
ensureParentDir(crt_path)
pfx_path = crt_path.replace(".crt", ".pfx")
# TODO: maybe use an actual password. For now, just something that won't be # brute-forced.
password = "193dbfc6-8ff7-4a95-8f32-6b4468626bd0"
if force ornot os.path.isfile(crt_path):
log(
logging.INFO, "msix",
{"crt_path": crt_path}, "Creating new self signed certificate at: {}".format(crt_path),
)
thumbprints = [
thumbprint.strip() for thumbprint in powershell(
(
r"Get-ChildItem -Path Cert:\CurrentUser\My" '| Where-Object {{$_.Subject -Match "{}"}}' '| Where-Object {{$_.FriendlyName -Match "{}"}}' "| Select-Object -ExpandProperty Thumbprint"
).format(vendor, friendly_name)
).splitlines()
] if len(thumbprints) > 1: raise Exception( "Multiple certificates with friendly name found: {}".format(
friendly_name
)
)
# As a convenience to the user, tell how to use this certificate if it's not # already trusted, and how to work with MSIX files more generally. if verbose:
root_thumbprints = [
root_thumbprint.strip() for root_thumbprint in powershell(
r"Get-ChildItem -Path Cert:\LocalMachine\Root\{} " "| Select-Object -ExpandProperty Thumbprint".format(thumbprint),
check=False,
).splitlines()
] if thumbprint notin root_thumbprints:
log(
logging.INFO, "msix",
{"thumbprint": thumbprint}, "Certificate with thumbprint not found in trusted roots: {thumbprint}",
)
log(
logging.INFO, "msix",
{"crt_path": crt_path, "output": output},
r"""\ # Usage
To trust this certificate (requires an elevated shell):
powershell -c 'Import-Certificate -FilePath "{crt_path}" -Cert Cert:\LocalMachine\Root\'
To verify this MSIX signature exists andis trusted:
powershell -c 'Get-AuthenticodeSignature -FilePath "{output}" | Format-List *'
To install this MSIX:
powershell -c 'Add-AppPackage -path "{output}"'
To see details after installing:
powershell -c 'Get-AppPackage -name Mozilla.MozillaFirefox(Beta,...)' """.strip(),
)
ifnot makeappx: raise ValueError("makeappx is required; ""set MAKEAPPX or PATH")
openssl = find_sdk_tool("openssl", log=log)
ifnot openssl: raise ValueError("openssl is required; ""set OPENSSL or PATH")
if"sign"notin subprocess.run(makeappx, capture_output=True).stdout.decode( "utf-8"
): raise ValueError( "makeappx must support 'sign' operation. ", "You probably need to build Mozilla's version of it: ", "https://github.com/mozilla/msix-packaging/tree/johnmcpms/signing",
)
# These are baked into enough places under `browser/` that we need not # extract constants.
cn = "Mozilla Corporation"
ou = "MSIX Packaging"
friendly_name = "Mozilla Corporation MSIX Packaging Test Certificate" # Password is needed when generating the cert, but # "makeappx" explicitly does _not_ support passing it # so it ends up getting removed when we create the pfx
password = "temp"
if force ornot os.path.isfile(pfx_path):
log(
logging.INFO, "msix",
{"pfx_path": pfx_path}, "Creating new self signed certificate at: {}".format(pfx_path),
)
# Ultimately, we only end up using the CA certificate # and the pfx (aka pkcs12) bundle containing the signing key # and certificate. The other things we create along the way # are not used for subsequent signing for testing. # To get those, we have to do a few things: # 1) Create a new CA key and certificate # 2) Create a new signing key # 3) Create a CSR with that signing key # 4) Create the certificate with the CA key+cert from the CSR # 5) Convert the signing key and certificate to a pfx bundle
args = [ "req", "-x509", "-days", "7200", "-sha256", "-newkey", "rsa:4096", "-keyout",
ca_key_path, "-out",
ca_crt_path, "-outform", "PEM", "-subj",
f"/OU={ou} CA/CN={cn} CA", "-passout",
f"pass:{password}",
]
run_openssl(args)
args = [ "genrsa", "-des3", "-out",
key_path, "-passout",
f"pass:{password}",
]
run_openssl(args)
args = [ "req", "-new", "-key",
key_path, "-out",
csr_path, "-subj", # We actually want these in the opposite order, to match what's # included in the AppxManifest. Openssl ends up reversing these # for some reason, so we put them in backwards here.
f"/OU={ou}/CN={cn}", "-passin",
f"pass:{password}",
]
run_openssl(args)
args = [ "x509", "-req", "-sha256", "-days", "7200", "-in",
csr_path, "-CA",
ca_crt_path, "-CAcreateserial", "-CAkey",
ca_key_path, "-out",
crt_path, "-outform", "PEM", "-passin",
f"pass:{password}",
]
run_openssl(args)
args = [ "pkcs12", "-export", "-inkey",
key_path, "-in",
crt_path, "-name",
friendly_name, "-passin",
f"pass:{password}", # All three of these options (-keypbe, -certpbe, and -passout) # are necessary to create a pfx bundle that won't even prompt # for a password. If we miss one, we will still get a password # prompt for the blank password. "-keypbe", "NONE", "-certpbe", "NONE", "-passout", "pass:", "-out",
pfx_path,
]
run_openssl(args)
if verbose:
log(
logging.INFO, "msix",
{ "ca_crt_path": ca_crt_path, "ca_crt": mozpath.basename(ca_crt_path), "output_path": output, "output": mozpath.basename(output),
},
r"""\ # Usage
First, transfer the root certificate ({ca_crt_path}) and signed MSIX
({output_path}) to a Windows machine.
To trust this certificate ({ca_crt_path}), run the following in an elevated shell:
powershell -c 'Import-Certificate -FilePath "{ca_crt}" -Cert Cert:\LocalMachine\Root\'
To verify this MSIX signature exists andis trusted:
powershell -c 'Get-AuthenticodeSignature -FilePath "{output}" | Format-List *'
To install this MSIX:
powershell -c 'Add-AppPackage -path "{output}"'
To see details after installing:
powershell -c 'Get-AppPackage -name Mozilla.MozillaFirefox(Beta,...)' """.strip(),
)
def sign_msix(output, force=False, log=None, verbose=False): """Sign an MSIX with a locally generated self-signed certificate."""
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.