我尝试创建一个小型 C# 应用程序来修改嵌入式清单,这样它就不会请求管理员权限。这是我最终想出的解决方案,进行了一系列 Win32 调用以提取清单并替换现有清单。已经够长了,所以我省略了我实际修改清单的部分(只是一些基本的 XML 操作)。
这里有两个静态方法:LoadManifestResource,它加载一个可执行文件的嵌入式清单的字符串表示和SaveManifestResource,它保存一个清单资源的字符串表示在指定的可执行文件中,覆盖旧的。
这是一个快速而肮脏的解决方案,对我来说效果很好,但很可能并非在所有情况下都有效。
public static class Library
{
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool FreeLibrary(IntPtr hModule);
[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, int lpName, int lpType);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32.dll")]
static extern IntPtr LockResource(IntPtr hResData);
[DllImport("Kernel32.dll", EntryPoint = "SizeofResource", SetLastError = true)]
private static extern uint SizeofResource(IntPtr hModule, IntPtr hResource);
[System.Flags]
enum LoadLibraryFlags : uint
{
DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
}
public static unsafe string LoadManifestResource(string fileName)
{
// load library to retrieve manifest from
var libraryHandle = LoadLibraryEx(fileName, IntPtr.Zero, LoadLibraryFlags.LOAD_LIBRARY_AS_DATAFILE);
if (libraryHandle.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't load library");
}
try
{
// find manifest
var resource = FindResource(libraryHandle, 1, 24);
if (resource.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't find manifest resource");
}
// load manifest
var loadedManifest = LoadResource(libraryHandle, resource);
if (loadedManifest.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't load manifest resource");
}
// lock manifest
var lockedManifest = LockResource(loadedManifest);
if (lockedManifest.ToInt32() == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "couldn't lock manifest resource");
}
// calculate size of manifest, copy to byte array and convert to string
int manifestSize = (int)SizeofResource(libraryHandle, resource);
byte[] data = new byte[manifestSize];
Marshal.Copy(lockedManifest, data, 0, manifestSize);
var manifest = Encoding.UTF8.GetString(data);
return manifest;
}
finally
{
FreeLibrary(libraryHandle);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage, IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, int lpType, int lpName, ushort wLanguage, IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static unsafe void SaveManifestResource(string file, string manifest)
{
var hUpdate = BeginUpdateResource(file, false);
byte[] bytes = Encoding.UTF8.GetBytes(manifest);
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
try
{
Marshal.Copy(bytes, 0, ptr, bytes.Length);
if (!UpdateResource(hUpdate, 24, 1, 0, ptr, (uint)bytes.Length))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (!EndUpdateResource(hUpdate, false))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
}