配置 MAUI 项目使用管理员权限启动

2022年11月10日 1718点热度 0人点赞 0条评论
内容纲要

问题背景

在 Windows 中,开发的应用可以使用 app.manifest 资产文件配置程序启动时,使用何种角色权限启动。

效果如下:

file

正常情况下,在 app.manifest 加上以下配置即可:

如果项目中没有这个文件,可以在项目中新建项-清单文件。

  <trustInfo xmlns='urn:schemas-microsoft-com:asm.v2'>
    <security>
      <requestedPrivileges xmlns='urn:schemas-microsoft-com:asm.v3'>
        <requestedExecutionLevel level='requireAdministrator' uiAccess='false' />
      </requestedPrivileges>
    </security>
  </trustInfo>

file

但是在 MAUI 应用中,是加不上去的,如果加上去,就会出现报错。

Platforms\Windows\app.manifest : manifest authoring error c1010001: Values of attribute "level" not equal in different manifest snippets. 

因为 .NET 编译器中,已经默认为程序生成一个 app.manifest 文件,其文件内容中已经包含了 trustInfo 配置。

如果项目中开启了 <WindowsAppSDKSelfConatined>true</WindowsAppSDKSelfConatined>,那么应该查看 Microsoft.WindowsAppSDK.SelfContained.targets 文件:
file
file

所以如果要自定义 app.manifest ,要么就是把 Microsoft.WindowsAppSDK.SelfContained.targets 改了,但是这样不太好。

定制编译过程

如果观察编译过程,会发现 manifest 文件会生成到 obj 目录。
file

其中 mergeapp.manifest 便是项目中的 app.manifest ,.NET 编译的时候将开发者的文件改名字了。

程序编译时,首先从 Microsoft.WindowsAppSDK.SelfContained.targets 中生成默认的 app.manifest
接着将开发者项目中的 app.manifest 复制为 mergeapp.manifest 文件,然后将 mergeapp.manifest 合并到 app.manifest

如果 app.manifest 中已经存在配置,那么 mergeapp.manifest 中重复的记录就会导致编译报错。

既然了解到了编译过程,那么我们可以在编译过程中做手脚。
我们可以在编译生成 app.manifest 但是还没有编译主程序的时候,将 app.manifest 中的配置替换掉,替换命令是:

powershell -Command ";(gc app.manifest) -replace 'level=''asInvoker''', 'level=''requireAdministrator''' | Out-File -encoding ASCII app.manifest";

file

MSBuild 编译使用到的步骤可以参考官方文档:
https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-targets?view=vs-2022

在编译过程中,有两个重要的环境变量:
_DeploymentManifestFiles :清单文件所在目录;
ApplicationManifestapp.manifest 文件路径。

可以在 .csproj 文件中加入以下脚本,这样会在程序编译时,自动修改清单文件。

    <Target Name="RequireAdministrator" BeforeTargets="GenerateManifests" Condition="'$(PublishDir)' != ''">
        <Exec WorkingDirectory="./" Command="echo $(ApplicationManifest)" />
        <Exec WorkingDirectory="./" Command="echo $(_DeploymentManifestFiles)" />
        <Exec WorkingDirectory="$(_DeploymentManifestFiles)" Command="dir" />
        <Exec WorkingDirectory="$(_DeploymentManifestFiles)" Command="powershell -Command "(gc app.manifest) -replace 'level=''asInvoker''', 'level=''requireAdministrator''' | Out-File -encoding ASCII app.manifest"" />
    </Target>

BeforeTargets="GenerateManifests" 表明在 GenerateManifests 之前,执行里面的自定义命令。

Condition="'$(PublishDir)' != ''" 表示触发条件,在 MAUI 中,只有发布的时候才会有这个变量,也可以改成 Condition="'$(Release)' != ''"

注意,有些情况下 _DeploymentManifestFiles 目录会不存在,因此可以多次测试一下。

当然,最保险的方法:

    <Target Name="RequireAdministrator" BeforeTargets="GenerateManifests" Condition="'$(PublishDir)' != ''">
        <Exec WorkingDirectory="./" Command=" powershell -Command "(gc $(ApplicationManifest)) -replace 'level=''asInvoker''', 'level=''requireAdministrator''' | Out-File -encoding UTF8 $(ApplicationManifest)"" />
    </Target>

编译过程主要在以下三步,其中只有 GenerateManifests 能够在 .csproj 中使用。

<Target Name="GenerateManifests"
        Condition="'$(GenerateClickOnceManifests)'=='true' or '@(NativeReference)'!='' or '@(ResolvedIsolatedComModules)'!='' or '$(GenerateAppxManifest)' == 'true'"
        DependsOnTargets="$(GenerateManifestsDependsOn)"/>

===================================================
GenerateApplicationManifest
Generates a ClickOnce or native application manifest.
An application manifest specifies declarative application identity, dependency and security information.
===================================================
<Target Name="GenerateApplicationManifest"
        DependsOnTargets="
                _DeploymentComputeNativeManifestInfo;
                _DeploymentComputeClickOnceManifestInfo;
                ResolveComReferences;
                ResolveNativeReferences;
                _GenerateResolvedDeploymentManifestEntryPoint"
        Inputs="
                $(MSBuildAllProjects);
                @(AppConfigWithTargetPath);
                $(_DeploymentBaseManifest);
                @(ResolvedIsolatedComModules);
                @(_DeploymentManifestDependencies);
                @(_DeploymentResolvedManifestEntryPoint);
                @(_DeploymentManifestFiles)"
        Outputs="@(ApplicationManifest)">

能够拿到参数:

                $(_DeploymentBaseManifest);
                @(ResolvedIsolatedComModules);
                @(_DeploymentManifestDependencies);
                @(_DeploymentResolvedManifestEntryPoint);
                @(_DeploymentManifestFiles)
                @(ApplicationManifest)

痴者工良

高级程序员劝退师

文章评论