|
#r "Microsoft.WindowsAPICodePack.Shell.dll" |
|
#r "Microsoft.WindowsAPICodePack.dll" |
|
#r "Interop.IWshRuntimeLibrary.dll" |
|
|
|
using IWshRuntimeLibrary; |
|
using Microsoft.WindowsAPICodePack.Shell; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Runtime.InteropServices; |
|
|
|
internal class ReverseSorter : System.Collections.IComparer |
|
{ |
|
int System.Collections.IComparer.Compare(object x, object y) |
|
{ |
|
return new System.Collections.CaseInsensitiveComparer().Compare(y, x); |
|
} |
|
} |
|
|
|
private static readonly KeyValuePair<string, string>[] SystemFolderMapping = new[] |
|
{ |
|
new KeyValuePair<string, string>(Environment.ExpandEnvironmentVariables("%programfiles%").ToLower(), "{905E63B6-C1BF-494E-B29C-65B732D3D21A}"), |
|
new KeyValuePair<string, string>(Environment.ExpandEnvironmentVariables("%windir%\\system32").ToLower(), "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"), |
|
new KeyValuePair<string, string>(Environment.ExpandEnvironmentVariables("%windir%").ToLower(), "{F38BF404-1D43-42F2-9305-67DE0B28FC23}"), |
|
new KeyValuePair<string, string>(Environment.ExpandEnvironmentVariables("%systemroot%").ToLower(), "{F38BF404-1D43-42F2-9305-67DE0B28FC23}") |
|
}; |
|
|
|
private static readonly WshShell wsh = new WshShell(); |
|
|
|
private static readonly string WinXFolder = Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\Windows\WinX"); |
|
private static readonly System.Collections.IComparer ReverseSort = new ReverseSorter(); |
|
|
|
public static class WinXHasher |
|
{ |
|
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError=true)] |
|
internal static extern int HashData([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 1)] [In] byte[] pbData, int cbData, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 3)] [Out] byte[] piet, int outputLen); |
|
|
|
private static bool _isFileList = false; |
|
private static string _groupFolderPath; |
|
public static void HashLnk(string lnkFile) |
|
{ |
|
if (Path.GetExtension(lnkFile)?.ToLower() != ".lnk") |
|
{ |
|
if (_isFileList) return; |
|
_isFileList = true; |
|
if (_groupFolderPath == null) { |
|
_groupFolderPath = Path.Combine(WinXFolder, "Group"+GetNextMaxGroup()); |
|
Directory.CreateDirectory(_groupFolderPath); |
|
} |
|
var lines = System.IO.File.ReadAllLines(lnkFile); |
|
for (var lineNum=0; lineNum < lines.Length-2; lineNum++) //skip first 2 header lines |
|
{ |
|
var line = lines[lineNum+2]; //skip first 2 header lines |
|
var chunks = line.Split('|'); |
|
if (chunks.Length != 5) { |
|
Console.WriteLine("Listing file needs to be lines in following bar separated format (no quotes):"); |
|
Console.WriteLine("Display Name | exe path | Elevated (True/False) | Arguments"); |
|
return; |
|
} |
|
var title = chunks[0].Trim(); |
|
var lnkPath = Path.Combine(_groupFolderPath, |
|
//the menu structure works in reverse of the top to bottom order in the shortcuts listing file |
|
(lines.Length - lineNum).ToString().PadLeft(2, '0') + " - " + title + ".lnk"); |
|
var wshShortcut = (IWshShortcut)wsh.CreateShortcut(lnkPath); |
|
wshShortcut.Description = title; |
|
wshShortcut.TargetPath = chunks[1].Trim(); |
|
wshShortcut.WorkingDirectory = chunks[2].Trim(); |
|
bool isElevated; bool.TryParse(chunks[3].Trim(), out isElevated); |
|
wshShortcut.Arguments = chunks[4].Trim(); |
|
|
|
Console.WriteLine($"lnk: {lnkPath}, target: {wshShortcut.TargetPath}, args: {wshShortcut.Arguments}"); |
|
wshShortcut.Save(); |
|
|
|
if (isElevated) ElevateLnk(lnkPath); |
|
|
|
HashLnk(lnkPath); |
|
} |
|
return; |
|
} |
|
|
|
var lnk = (IWshShortcut)wsh.CreateShortcut(lnkFile); |
|
|
|
var text = lnk.TargetPath; |
|
|
|
// ReSharper disable once LoopCanBeConvertedToQuery |
|
foreach(var kv in SystemFolderMapping) text = text.ToLower().Replace(kv.Key, kv.Value); |
|
|
|
//this is the magic kicker that makes the shortcuts special and show up in the start menu |
|
//lifted following hash algorithm from: http://winaero.com/download.php?view.21 |
|
if (lnk.Arguments.Length > 0) text += lnk.Arguments; |
|
//this magic string appears to be necessary for the hash to be accepted by Windows |
|
text += "do not prehash links. this should only be done by the user."; |
|
text = text.ToLower(); |
|
var inBytes = Encoding.GetEncoding(1200).GetBytes(text); |
|
var byteCount = inBytes.Length; |
|
var outBytes = new byte[byteCount]; |
|
var hashResult = HashData(inBytes, byteCount, outBytes, byteCount); |
|
if (hashResult != 0) throw new Exception("Shlwapi::HashData failed: {Marshal.GetLastWin32Error()}"); |
|
using (var propertyWriter = ShellFile.FromFilePath(lnkFile).Properties.GetPropertyWriter()) |
|
{ |
|
propertyWriter.WriteProperty("System.Winx.Hash", BitConverter.ToUInt32(outBytes, 0)); |
|
} |
|
} |
|
|
|
private static int GetNextMaxGroup() |
|
{ |
|
var directories = Directory.GetDirectories(WinXFolder, "Group*", SearchOption.TopDirectoryOnly); |
|
Array.Sort(directories, ReverseSort); |
|
foreach (var path in directories) |
|
{ |
|
var s = Path.GetFileName(path)?.Substring(5).Trim(); |
|
if (s == null) continue; |
|
int result; |
|
if (int.TryParse(s, out result)) return result+1; |
|
} |
|
return 0; |
|
} |
|
|
|
//from: http://blog.coretech.dk/hra/create-shortcut-with-elevated-rights/ |
|
private static void ElevateLnk(string lnkFile) |
|
{ |
|
using (FileStream fs = new FileStream(lnkFile, FileMode.Open, FileAccess.ReadWrite)) |
|
{ |
|
fs.Seek(21, SeekOrigin.Begin); |
|
fs.WriteByte(0x22); |
|
} |
|
} |
|
|
|
} |
|
|
|
WinXHasher.HashLnk(Env.ScriptArgs[0]); |