├── CBSEnum.res ├── CBSEnum_Icon.ico ├── Docs └── cbsenum-0.8-screen.png ├── .hgignore ├── CBSEnum.dpr ├── CBSEnum_JobProcessor.dfm ├── WildcardMatching.pas ├── DecouplePackagesJob.pas ├── README.md ├── OsUtils.pas ├── TakeOwnershipJob.pas ├── CBSEnum_JobProcessor.pas ├── AclHelpers.pas ├── CBSEnum_Main.dfm ├── CBSEnum.dproj └── CBSEnum_Main.pas /CBSEnum.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himselfv/cbsenum/HEAD/CBSEnum.res -------------------------------------------------------------------------------- /CBSEnum_Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himselfv/cbsenum/HEAD/CBSEnum_Icon.ico -------------------------------------------------------------------------------- /Docs/cbsenum-0.8-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himselfv/cbsenum/HEAD/Docs/cbsenum-0.8-screen.png -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax:glob 2 | 3 | __history/* 4 | *.local 5 | *.dcu 6 | *.rsm 7 | *.identcache 8 | *.vlb 9 | 10 | # Binary 11 | *.dll 12 | *.exe 13 | 14 | # Releases 15 | Win32/* 16 | Win64/* -------------------------------------------------------------------------------- /CBSEnum.dpr: -------------------------------------------------------------------------------- 1 | program CBSEnum; 2 | 3 | uses 4 | Vcl.Forms, 5 | CBSEnum_Main in 'CBSEnum_Main.pas' {MainForm}, 6 | CBSEnum_JobProcessor in 'CBSEnum_JobProcessor.pas' {JobProcessorForm}, 7 | AclHelpers in 'AclHelpers.pas', 8 | CommonResources in '..\ManifestEnum\CommonResources.pas' {ResourceModule}, 9 | DelayLoadTree in '..\ManifestEnum\Views\DelayLoadTree.pas', 10 | TakeOwnershipJob in 'TakeOwnershipJob.pas', 11 | DecouplePackagesJob in 'DecouplePackagesJob.pas', 12 | WildcardMatching in 'WildcardMatching.pas'; 13 | 14 | {$R *.res} 15 | 16 | begin 17 | Application.Initialize; 18 | Application.MainFormOnTaskbar := True; 19 | Application.CreateForm(TMainForm, MainForm); 20 | Application.CreateForm(TJobProcessorForm, JobProcessorForm); 21 | Application.Run; 22 | end. 23 | -------------------------------------------------------------------------------- /CBSEnum_JobProcessor.dfm: -------------------------------------------------------------------------------- 1 | object JobProcessorForm: TJobProcessorForm 2 | Left = 0 3 | Top = 0 4 | Caption = 'Processing' 5 | ClientHeight = 336 6 | ClientWidth = 527 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | OldCreateOrder = False 14 | OnClose = FormClose 15 | OnCloseQuery = FormCloseQuery 16 | OnCreate = FormCreate 17 | OnDestroy = FormDestroy 18 | PixelsPerInch = 96 19 | TextHeight = 13 20 | object mmLog: TMemo 21 | Left = 0 22 | Top = 0 23 | Width = 527 24 | Height = 336 25 | Align = alClient 26 | ReadOnly = True 27 | ScrollBars = ssVertical 28 | TabOrder = 0 29 | OnChange = mmLogChange 30 | end 31 | object UpdateTimer: TTimer 32 | Enabled = False 33 | Interval = 200 34 | OnTimer = UpdateTimerTimer 35 | Left = 8 36 | Top = 8 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /WildcardMatching.pas: -------------------------------------------------------------------------------- 1 | unit WildcardMatching; 2 | 3 | interface 4 | 5 | function WildcardMatchCase(a, w: PChar): boolean; 6 | 7 | implementation 8 | 9 | function WildcardMatchCase(a, w: PChar): boolean; 10 | label new_segment, test_match; 11 | var i: integer; 12 | star: boolean; 13 | begin 14 | new_segment: 15 | star := false; 16 | if w^='*' then begin 17 | star := true; 18 | repeat Inc(w) until w^ <> '*'; 19 | end; 20 | 21 | test_match: 22 | i := 0; 23 | while (w[i]<>#00) and (w[i]<>'*') do 24 | if a[i] <> w[i] then begin 25 | if a[i]=#00 then begin 26 | Result := false; 27 | exit; 28 | end; 29 | if (w[i]='?') and (a[i] <> '.') then begin 30 | Inc(i); 31 | continue; 32 | end; 33 | if not star then begin 34 | Result := false; 35 | exit; 36 | end; 37 | Inc(a); 38 | goto test_match; 39 | end else 40 | Inc(i); 41 | 42 | if w[i]='*' then begin 43 | Inc(a, i); 44 | Inc(w, i); 45 | goto new_segment; 46 | end; 47 | 48 | if a[i]=#00 then begin 49 | Result := true; 50 | exit; 51 | end; 52 | 53 | if (i > 0) and (w[i-1]='*') then begin 54 | Result := true; 55 | exit; 56 | end; 57 | 58 | if not star then begin 59 | Result := false; 60 | exit; 61 | end; 62 | 63 | Inc(a); 64 | goto test_match; 65 | end; 66 | 67 | end. 68 | -------------------------------------------------------------------------------- /DecouplePackagesJob.pas: -------------------------------------------------------------------------------- 1 | unit DecouplePackagesJob; 2 | 3 | interface 4 | uses Windows, CBSEnum_JobProcessor, UniStrUtils; 5 | 6 | type 7 | TDecouplePackagesJob = class(TProcessingThread) 8 | protected 9 | FPackageNames: TStringArray; 10 | procedure DecouplePackages(const AKey: string; const APackageNames: TStringArray); 11 | public 12 | constructor Create(const APackageNames: TStringArray); 13 | procedure Execute; override; 14 | end; 15 | 16 | implementation 17 | uses SysUtils, Classes, Registry, CBSEnum_Main; 18 | 19 | constructor TDecouplePackagesJob.Create(const APackageNames: TStringArray); 20 | begin 21 | inherited Create; 22 | Self.FPackageNames := APackageNames; 23 | end; 24 | 25 | //Ownership has to be already taken. 26 | //PackageNames: list of packages to be decoupled from their parents. Lowercase. Empty == all packages. 27 | procedure TDecouplePackagesJob.DecouplePackages(const AKey: string; const APackageNames: TStringArray); 28 | var reg: TRegistry; 29 | subkeys: TStringList; 30 | subkey: string; 31 | begin 32 | Log('Trying '+AKey+'...'); 33 | subkeys := nil; 34 | reg := TRegistry.Create; 35 | try 36 | reg.RootKey := hkCbsRoot; 37 | if not reg.OpenKey(AKey, false) then begin 38 | Log('No such key.'); 39 | exit; //no key, whatever 40 | end; 41 | 42 | subkeys := TStringList.Create; 43 | reg.GetKeyNames(subkeys); 44 | 45 | for subkey in subkeys do begin 46 | if Terminated then break; 47 | Log(subkey+'...'); 48 | if (Length(APackageNames) = 0) or IsPackageInList(APackageNames, subkey) then try 49 | reg.DeleteKey(subkey+'\Owners'); 50 | Log('Owners removed.'); 51 | except 52 | on E: EOSError do 53 | if E.ErrorCode = ERROR_FILE_NOT_FOUND then 54 | continue 55 | else 56 | raise; 57 | end; 58 | end; 59 | 60 | finally 61 | FreeAndNil(subkeys); 62 | FreeAndNil(reg); 63 | end; 64 | end; 65 | 66 | procedure TDecouplePackagesJob.Execute; 67 | begin 68 | DecouplePackages(sCbsKey+'\Packages', FPackageNames); 69 | DecouplePackages(sCbsKey+'\PackageNames', FPackageNames); 70 | end; 71 | 72 | end. 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CBSEnum # 2 | 3 | CBSEnum is a tool to view and manage Windows Component-Based Servicing packages. 4 | 5 | Component-Based Servicing is a technology since Windows Vista which most resembles Linux-style package managers. It builds upon WinSxS (Side by side assemblies) to allow installation, deinstallation and updating of numerous Windows components independently. 6 | 7 | It presents a moderately componentized view into Windows and allows uninstalling parts of system which outside of Windows Embedded were previously seen as monolithic. 8 | 9 | In Windows, this technology is hidden from general public. There's a command-line tool dism.exe to manage CBS packages, but most packages are marked as hidden even from DISM. 10 | 11 | ![CBSEnum screenshot](Docs/cbsenum-0.8-screen.png) 12 | 13 | CBSEnum is a graphical interface for DISM which presents packages in a visually simple format, allows to uninstall or mass-uninstall any. It also shows hidden packages, lets you make them visible or restore to original visibility state. 14 | 15 | ### Requirements and warnings ### 16 | 17 | CBSEnum must be run as administrator. 18 | 19 | Before anything can be done with packages, you will have to take ownership of `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing` registry key and all subkeys and give yourself write permissions. CBSEnum can do this for you: choose "Edit -> Take Ownership". 20 | 21 | Before most packages can be deleted, they have to be detached from their mothership package ("Windows Home", "Windows Professional" or "Windows Enterprise"). This can be done with [install_wim_tweak tool](http://www.wincert.net/forum/topic/12021-install-wim-tweakexe/) but now CBSEnum supports this too: "Edit -> Decouple all packages". 22 | 23 | Before DISM will work with most packages, they have to be made DISM-visible. This can be done from CBSEnum by right-clicking any package and doing "Visibility -> Make visible". You can also make all packages Visible from Edit menu. 24 | 25 | CBSEnum preserves original package visibility in the same way instal_wim_tweak does, in DefVis keys. 26 | 27 | When uninstalling packages, exert your usual caution. Uninstalled packages cannot be installed back without their source cabs, which most people don't have. Save for reinstalling the OS, your best bet would be system repair from installation media. 28 | 29 | ### Bulk removal ### 30 | 31 | Starting with 0.9, CBSEnum supports bulk removal scripts. Each line is a package mask: 32 | 33 | ``` 34 | # Telemetry 35 | Microsoft-OneCore-AllowTelemetry* 36 | Microsoft-Windows-Prerelease* 37 | Microsoft-Windows-DiagTrack* 38 | # Microsoft-WindowsFeedback* # Feedback is a useful app to have, but may be seen as telemetry 39 | Microsoft-OneCore-TroubleShooting* # Some consider this also part of telemetry. 40 | Microsoft-Windows-TroubleShooting* 41 | Microsoft-Windows-ContactSupport* # Contact Microsoft support 42 | ``` 43 | 44 | Such scripts, listing all that you feel needs to be removed with comments about the reasons, can be tested in a virtual machine until a satisfying configuration is achieved and then deployed to the actual target. -------------------------------------------------------------------------------- /OsUtils.pas: -------------------------------------------------------------------------------- 1 | unit OsUtils; 2 | 3 | interface 4 | uses SysUtils, Windows; 5 | 6 | function GetSystemDir: string; 7 | function GetWindowsDir: string; 8 | function GetModuleFilenameStr(hModule: HMODULE = 0): string; 9 | function AppFolder: string; 10 | 11 | function StartProcess(const AProgramName, ACommandLine: string): TProcessInformation; 12 | procedure RegeditOpenAndNavigate(const ARegistryPath: string); 13 | procedure ShellOpen(const sCommand: string; const sParams: string = ''); 14 | procedure ExplorerAtFile(const AFilename: string); 15 | 16 | 17 | implementation 18 | uses Registry, ShellAPI; 19 | 20 | function GetSystemDir: string; 21 | var 22 | Buffer: array[0..MAX_PATH] of Char; 23 | begin 24 | GetSystemDirectory(Buffer, MAX_PATH - 1); 25 | SetLength(Result, StrLen(Buffer)); 26 | Result := Buffer; 27 | end; 28 | 29 | function GetWindowsDir: string; 30 | var 31 | Buffer: array[0..MAX_PATH] of Char; 32 | begin 33 | GetWindowsDirectory(Buffer, MAX_PATH - 1); 34 | SetLength(Result, StrLen(Buffer)); 35 | Result := Buffer; 36 | end; 37 | 38 | //Max length, in symbols, of supported image path size. 39 | const 40 | MAX_PATH_LEN = 8192; 41 | 42 | function GetModuleFilenameStr(hModule: HMODULE = 0): string; 43 | var nSize, nRes: dword; 44 | begin 45 | nSize := 256; 46 | SetLength(Result, nSize); 47 | 48 | nRes := GetModuleFilenameW(hModule, @Result[1], nSize); 49 | while (nRes <> 0) and (nRes >= nSize) and (nSize < MAX_PATH_LEN) do begin 50 | nSize := nSize * 2; 51 | SetLength(Result, nSize); 52 | nRes := GetModuleFilenameW(hModule, @Result[1], nSize); 53 | end; 54 | 55 | if nRes = 0 then begin 56 | Result := ''; 57 | exit; 58 | end; 59 | 60 | if nRes >= nSize then begin 61 | Result := ''; 62 | exit; 63 | end; 64 | 65 | SetLength(Result, nRes); 66 | end; 67 | 68 | function AppFolder: string; 69 | begin 70 | Result := GetModuleFilenameStr(); 71 | if Result <> '' then 72 | Result := SysUtils.ExtractFilePath(Result); 73 | end; 74 | 75 | function StartProcess(const AProgramName, ACommandLine: string): TProcessInformation; 76 | var startupInfo: TStartupInfo; 77 | begin 78 | FillChar(startupInfo, SizeOf(startupInfo), 0); 79 | FillChar(Result, SizeOf(Result), 0); 80 | if not CreateProcess(PChar(AProgramName), PChar(ACommandLine), 81 | nil, nil, false, 0, nil, nil, startupInfo, Result) then 82 | RaiseLastOsError(); 83 | end; 84 | 85 | procedure RegeditOpenAndNavigate(const ARegistryPath: string); 86 | var reg: TRegistry; 87 | begin 88 | //That's the only damn way 89 | //Well, there's also regjump.exe from SysInternals but it shows EULA and it's 90 | //a dependency. 91 | //It can also be done directly using UI automation. 92 | reg := TRegistry.Create; 93 | try 94 | reg.RootKey := HKEY_CURRENT_USER; 95 | if not reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Applets\Regedit', true) then 96 | raise Exception.Create('Cannot point regedit at a key.'); 97 | reg.WriteString('LastKey', ARegistryPath); 98 | finally 99 | FreeAndNil(reg); 100 | end; 101 | StartProcess(GetWindowsDir()+'\regedit.exe', 'regedit.exe'); 102 | end; 103 | 104 | procedure ShellOpen(const sCommand: string; const sParams: string = ''); 105 | begin 106 | ShellExecute(0, 'open', PChar(sCommand), PChar(sParams), '', SW_SHOW); 107 | end; 108 | 109 | procedure ExplorerAtFile(const AFilename: string); 110 | begin 111 | ShellExecute(0, '', PChar('explorer.exe'), PChar('/select,"'+AFilename+'"'), 112 | '', SW_SHOW); 113 | end; 114 | 115 | end. 116 | -------------------------------------------------------------------------------- /TakeOwnershipJob.pas: -------------------------------------------------------------------------------- 1 | unit TakeOwnershipJob; 2 | 3 | interface 4 | uses Windows, Registry, AccCtrl, CBSEnum_JobProcessor; 5 | 6 | {$IFDEF DEBUG} 7 | //{$DEFINE DEBUG_HELPER} 8 | {$ENDIF} 9 | 10 | type 11 | TTakeOwnershipJob = class(TProcessingThread) 12 | protected 13 | {$IFDEF DEBUG_HELPER} 14 | procedure AclHelpersLog(const AMsg: string); 15 | {$ENDIF} 16 | procedure TakeRegistryOwnership(); 17 | procedure TakeRegistryOwnershipOfKey(AReg: TRegistry; const AKey: string; ANewOwner: PSID); 18 | public 19 | procedure Execute; override; 20 | end; 21 | 22 | implementation 23 | uses SysUtils, Classes, AclHelpers, CBSEnum_Main; 24 | 25 | {$IFDEF DEBUG_HELPER} 26 | procedure TTakeOwnershipJob.AclHelpersLog(const AMsg: string); 27 | begin 28 | Self.Log(AMsg); 29 | end; 30 | {$ENDIF} 31 | 32 | procedure TTakeOwnershipJob.Execute; 33 | begin 34 | {$IFDEF DEBUG_HELPER} 35 | AclHelpers.OnLog := Self.AclHelpersLog; 36 | {$ENDIF} 37 | TakeRegistryOwnership(); 38 | {$IFDEF DEBUG_HELPER} 39 | AclHelpers.OnLog := nil; 40 | {$ENDIF} 41 | end; 42 | 43 | procedure TTakeOwnershipJob.TakeRegistryOwnership(); 44 | var hProcToken: THandle; 45 | pSidAdmin: PSID; 46 | reg: TRegistry; 47 | begin 48 | pSidAdmin := nil; 49 | 50 | //Before we take ownership, we need to claim that privilege 51 | Log('Opening process token'); 52 | if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hProcToken) then 53 | RaiseLastOsError(); 54 | Log('Setting SE_TAKE_OWNERSHIP_NAME'); 55 | if not SetPrivilege(hProcToken, SE_TAKE_OWNERSHIP_NAME, true) then 56 | RaiseLastOsError(); 57 | 58 | //Clear and release the handles later 59 | try 60 | Log('Getting BUILTIN\Administrators reference'); 61 | //We're going to give ownership to BUILTIN\Administrators, this is comparatively safe + fits our needs 62 | pSidAdmin := AllocateSidBuiltinAdministrators(); 63 | 64 | reg := TRegistry.Create; 65 | try 66 | reg.RootKey := hkCbsRoot; 67 | reg.Access := KEY_READ; 68 | TakeRegistryOwnershipOfKey(reg, '\'+sCbsKey, pSidAdmin); 69 | finally 70 | FreeAndNil(reg); 71 | end; 72 | 73 | finally 74 | if pSIDAdmin <> nil then 75 | FreeSid(pSIDAdmin); 76 | 77 | Log('Clearing SE_TAKE_OWNERSHIP_NAME'); 78 | SetPrivilege(hProcToken, SE_TAKE_OWNERSHIP_NAME, false); 79 | CloseHandle(hProcToken); 80 | end; 81 | Log('Done.'); 82 | end; 83 | 84 | //Called for every child key, recursively. Sets its owner to ANewOwner and gives the previous owner full rights. 85 | //Key must start with / 86 | procedure TTakeOwnershipJob.TakeRegistryOwnershipOfKey(AReg: TRegistry; const AKey: string; ANewOwner: PSID); 87 | var subkeys: TStringList; 88 | subkey: string; 89 | err: cardinal; 90 | pSidPreviousOwner: PSID; 91 | pPreviousOwnerDescriptor: PSECURITY_DESCRIPTOR; 92 | begin 93 | Log('Processing key '+AKey); 94 | //There's no way to "go one level upper" with TRegistry so we're stuck with non-efficient "open each key from the root" 95 | if not AReg.OpenKey(AKey, false) then 96 | RaiseLastOsError(); 97 | 98 | err := SwitchOwnership(sCbsRootSec+AKey, SE_REGISTRY_KEY, ANewOwner, pSIDPreviousOwner, 99 | pPreviousOwnerDescriptor); 100 | if err <> ERROR_SUCCESS then RaiseLastOsError(err); 101 | 102 | if pSidPreviousOwner <> nil then try 103 | Log('...ownership taken, granting permissions to previous owner'); 104 | //Give explicit full permissions to the previous owner 105 | err := AddExplicitPermissions(sCbsRootSec+AKey, SE_REGISTRY_KEY, pSidPreviousOwner, KEY_ALL_ACCESS); 106 | if err <> ERROR_SUCCESS then 107 | RaiseLastOsError(err); 108 | 109 | finally 110 | LocalFree(NativeUInt(pPreviousOwnerDescriptor)); 111 | pPreviousOwnerDescriptor := nil; 112 | pSidPreviousOwner := nil; 113 | end else 114 | Log('...already owned.'); 115 | 116 | //Give new owner full access (if they don't have it) 117 | err := AddExplicitPermissions(sCbsRootSec+AKey, SE_REGISTRY_KEY, ANewOwner, KEY_ALL_ACCESS); 118 | if err <> ERROR_SUCCESS then 119 | RaiseLastOsError(err); 120 | 121 | //Process subkeys 122 | subkeys := TStringList.Create; 123 | try 124 | AReg.GetKeyNames(subkeys); 125 | for subkey in subkeys do begin 126 | TakeRegistryOwnershipOfKey(AReg, AKey+'\'+subkey, ANewOwner); 127 | if Terminated then break; 128 | end; 129 | finally 130 | FreeAndNil(subkeys); 131 | end; 132 | end; 133 | 134 | 135 | end. 136 | -------------------------------------------------------------------------------- /CBSEnum_JobProcessor.pas: -------------------------------------------------------------------------------- 1 | unit CBSEnum_JobProcessor; 2 | // Runs threaded jobs 3 | 4 | interface 5 | 6 | uses 7 | Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 8 | Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; 9 | 10 | type 11 | TLogEvent = procedure(const AMessage: string) of object; 12 | TProcessingThread = class(TThread) 13 | protected 14 | FOnLog: TLogEvent; 15 | procedure Log(const AMessage: string); 16 | public 17 | constructor Create; 18 | property OnLog: TLogEvent read FOnLog write FOnLog; 19 | end; 20 | 21 | TJobProcessorForm = class(TForm) 22 | mmLog: TMemo; 23 | UpdateTimer: TTimer; 24 | procedure FormClose(Sender: TObject; var Action: TCloseAction); 25 | procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); 26 | procedure UpdateTimerTimer(Sender: TObject); 27 | procedure FormCreate(Sender: TObject); 28 | procedure FormDestroy(Sender: TObject); 29 | procedure mmLogChange(Sender: TObject); 30 | protected 31 | FThread: TProcessingThread; 32 | FLogSync: TRtlCriticalSection; 33 | FNewLogLines: TStringList; 34 | procedure ProcessingThreadLog(const AMessage: string); 35 | procedure PostLogEntries; 36 | public 37 | procedure Log(const msg: string); 38 | procedure Process(AJob: TProcessingThread); 39 | procedure EndProcessing; 40 | end; 41 | 42 | var 43 | JobProcessorForm: TJobProcessorForm; 44 | 45 | implementation 46 | 47 | {$R *.dfm} 48 | 49 | constructor TProcessingThread.Create; 50 | begin 51 | inherited Create({Suspended=}true); //always create suspended 52 | end; 53 | 54 | procedure TProcessingThread.Log(const AMessage: string); 55 | begin 56 | if Assigned(FOnLog) then 57 | FOnLog(AMessage); 58 | end; 59 | 60 | procedure TJobProcessorForm.FormCreate(Sender: TObject); 61 | begin 62 | InitializeCriticalSection(FLogSync); 63 | FNewLogLines := TStringList.Create; 64 | end; 65 | 66 | procedure TJobProcessorForm.FormDestroy(Sender: TObject); 67 | begin 68 | FreeAndNil(FNewLogLines); 69 | DeleteCriticalSection(FLogSync); 70 | end; 71 | 72 | procedure TJobProcessorForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 73 | begin 74 | CanClose := (not Assigned(FThread)) 75 | or (MessageBox(Self.Handle, PChar('The operation is still in progress, do you want to abort it?'), 76 | PChar('Confirm abort'), MB_ICONQUESTION + MB_YESNO) = ID_YES); 77 | end; 78 | 79 | procedure TJobProcessorForm.FormClose(Sender: TObject; var Action: TCloseAction); 80 | begin 81 | EndProcessing; 82 | end; 83 | 84 | procedure TJobProcessorForm.Process(AJob: TProcessingThread); 85 | begin 86 | if Assigned(FThread) then 87 | raise Exception.Create('Operation is still in progress'); 88 | FThread := AJob; 89 | FThread.OnLog := ProcessingThreadLog; 90 | FThread.Start; 91 | UpdateTimer.Enabled := true; 92 | end; 93 | 94 | procedure TJobProcessorForm.EndProcessing; 95 | begin 96 | if Assigned(FThread) then begin 97 | FThread.Terminate; 98 | FThread.WaitFor; 99 | FreeAndNil(FThread); 100 | end; 101 | UpdateTimer.Enabled := false; 102 | PostLogEntries(); //in case there's anything pending 103 | end; 104 | 105 | procedure TJobProcessorForm.UpdateTimerTimer(Sender: TObject); 106 | begin 107 | PostLogEntries; 108 | 109 | if FThread = nil then begin 110 | UpdateTimer.Enabled := false; 111 | exit; 112 | end; 113 | 114 | if FThread.FatalException <> nil then begin 115 | Log('Fatal exception '+FThread.FatalException.ClassName+': '+Exception(FThread.FatalException).Message); 116 | EndProcessing(); 117 | exit; 118 | end; 119 | 120 | if FThread.Finished then 121 | EndProcessing; 122 | end; 123 | 124 | procedure TJobProcessorForm.ProcessingThreadLog(const AMessage: string); 125 | begin 126 | EnterCriticalSection(FLogSync); 127 | try 128 | FNewLogLines.Add(AMessage); 129 | finally 130 | LeaveCriticalSection(FLogSync); 131 | end; 132 | end; 133 | 134 | procedure TJobProcessorForm.PostLogEntries; 135 | var line: string; 136 | begin 137 | EnterCriticalSection(FLogSync); 138 | try 139 | mmLog.Lines.BeginUpdate; 140 | for line in FNewLogLines do 141 | mmLog.Lines.Add(line); 142 | FNewLogLines.Clear; 143 | mmLog.Lines.EndUpdate; 144 | mmLog.Refresh; 145 | finally 146 | LeaveCriticalSection(FLogSync); 147 | end; 148 | end; 149 | 150 | procedure TJobProcessorForm.Log(const msg: string); 151 | begin 152 | mmLog.Lines.Add(msg); 153 | end; 154 | 155 | 156 | procedure TJobProcessorForm.mmLogChange(Sender: TObject); 157 | begin 158 | SendMessage(mmLog.Handle, EM_LINESCROLL, 0, mmLog.Lines.Count); 159 | end; 160 | 161 | end. 162 | -------------------------------------------------------------------------------- /AclHelpers.pas: -------------------------------------------------------------------------------- 1 | unit AclHelpers; 2 | //Windows security-related functions helper 3 | 4 | interface 5 | uses Windows, AccCtrl; 6 | 7 | const 8 | SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5)); 9 | 10 | SECURITY_BUILTIN_DOMAIN_RID = $00000020; 11 | 12 | DOMAIN_ALIAS_RID_ADMINS = $00000220; 13 | 14 | SE_TAKE_OWNERSHIP_NAME = 'SeTakeOwnershipPrivilege'; 15 | SE_BACKUP_NAME = 'SeBackupPrivilege'; 16 | SE_RESTORE_NAME = 'SeRestorePrivilege'; 17 | SE_SECURITY_NAME = 'SeSecurityPrivilege'; 18 | 19 | OBJECT_INHERIT_ACE = 1; 20 | CONTAINER_INHERIT_ACE = 2; 21 | 22 | ACCESS_ALLOWED_ACE_TYPE = 0; 23 | 24 | type 25 | TLuid = TLargeInteger; 26 | 27 | ACE_HEADER = record 28 | AceType: BYTE; 29 | AceFlags: BYTE; 30 | AceSize: WORD; 31 | end; 32 | PACE_HEADER = ^ACE_HEADER; 33 | 34 | ACCESS_ALLOWED_ACE = record 35 | Header: ACE_HEADER; 36 | Mask: ACCESS_MASK; 37 | SidStart: DWORD; 38 | end; 39 | PACCESS_ALLOWED_ACE = ^ACCESS_ALLOWED_ACE; 40 | 41 | {$IFDEF DEBUG} 42 | type 43 | TMsgEvent = procedure(const AMessage: string) of object; 44 | 45 | var 46 | OnLog: TMsgEvent; 47 | {$ENDIF} 48 | 49 | function LookupPrivilegeValue(lpSystemName, lpName: LPCWSTR): TLuid; 50 | 51 | function SetPrivilege(hToken: THandle; const APrivilege: TLuid; const AValue: boolean): boolean; overload; 52 | function SetPrivilege(hToken: THandle; const APrivilege: string; const AValue: boolean): boolean; overload; 53 | 54 | { 55 | This is a sort of a wrapper shortcut. Usage: 56 | if ClaimPrivilege(SE_PRIVILEGE_NAME, hPriv) <> 0 then begin 57 | ... 58 | ReleasePrivilege(hPriv); 59 | end; 60 | } 61 | type 62 | TPrivToken = record 63 | hProcToken: THandle; 64 | luid: TLuid; 65 | end; 66 | 67 | function ClaimPrivilege(const APrivilege: TLuid; out AToken: TPrivToken): boolean; overload; 68 | function ClaimPrivilege(const APrivilege: string; out AToken: TPrivToken): boolean; overload; inline; 69 | procedure ReleasePrivilege(const AToken: TPrivToken); overload; 70 | 71 | 72 | function AllocateSidBuiltin(Group: DWORD): PSID; 73 | function AllocateSidBuiltinAdministrators: PSID; 74 | 75 | function IsUserInBuiltinGroup(Group: DWORD): BOOL; 76 | function IsUserAdmin: BOOL; 77 | 78 | //pDescriptor has to be LocalFree()d, other out-parameters don't. 79 | function SetOwnership(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; aNewOwner: PSID): cardinal; 80 | function SwitchOwnership(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; 81 | aNewOwner: PSID; out aPreviousOwner: PSID; out pDescriptor: PSECURITY_DESCRIPTOR): cardinal; 82 | 83 | function AddExplicitPermissions(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; aTrustee: PSID; 84 | APermissions: cardinal; APreviousPermissions: PCardinal = nil): cardinal; 85 | 86 | 87 | implementation 88 | uses SysUtils, AclAPI; 89 | 90 | {$IFDEF DEBUG} 91 | procedure Log(const msg: string); 92 | begin 93 | if Assigned(OnLog) then 94 | OnLog(msg); 95 | end; 96 | {$ELSE} 97 | procedure Log(const msg: string); 98 | begin 99 | end; 100 | {$ENDIF} 101 | 102 | function CheckTokenMembership(TokenHandle: THandle; SidToCheck: PSID; 103 | out IsMember: BOOL): BOOL; stdcall; external advapi32; 104 | 105 | 106 | //A version of LookupPrivilegeValue which handles failure by throwing error 107 | function LookupPrivilegeValue(lpSystemName, lpName: LPCWSTR): TLuid; 108 | begin 109 | if not Windows.LookupPrivilegeValue(lpSystemName, lpName, Result) then 110 | RaiseLastOsError(); 111 | end; 112 | 113 | function SetPrivilege(hToken: THandle; const APrivilege: TLuid; const AValue: boolean): boolean; overload; 114 | var tp: TOKEN_PRIVILEGES; 115 | begin 116 | tp.PrivilegeCount := 1; 117 | tp.Privileges[0].Luid := APrivilege; 118 | if AValue then 119 | tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED 120 | else 121 | tp.Privileges[0].Attributes := 0; //disabled 122 | 123 | Result := AdjustTokenPrivileges(hToken, false, tp, sizeof(tp), PTokenPrivileges(nil)^, PCardinal(nil)^); 124 | end; 125 | 126 | function SetPrivilege(hToken: THandle; const APrivilege: string; const AValue: boolean): boolean; 127 | var sePrivilege: TLuid; 128 | begin 129 | if not Windows.LookupPrivilegeValue(nil, PChar(APrivilege), sePrivilege) then 130 | Result := false 131 | else 132 | Result := SetPrivilege(hToken, sePrivilege, AValue); 133 | end; 134 | 135 | function ClaimPrivilege(const APrivilege: TLuid; out AToken: TPrivToken): boolean; 136 | var err: integer; 137 | begin 138 | AToken.luid := APrivilege; 139 | if not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, AToken.hProcToken) then begin 140 | Result := false; 141 | exit; 142 | end; 143 | if not SetPrivilege(AToken.hProcToken, APrivilege, true) then begin 144 | err := GetLastError(); 145 | CloseHandle(AToken.hProcToken); //can modify LastError 146 | AToken.hProcToken := 0; 147 | SetLastError(err); 148 | Result := false; 149 | end; 150 | Result := true; 151 | end; 152 | 153 | function ClaimPrivilege(const APrivilege: string; out AToken: TPrivToken): boolean; overload; 154 | var sePrivilege: TLuid; 155 | begin 156 | if not Windows.LookupPrivilegeValue(nil, PChar(APrivilege), sePrivilege) then begin 157 | Result := false; 158 | exit; 159 | end; 160 | Result := ClaimPrivilege(sePrivilege, AToken); 161 | end; 162 | 163 | procedure ReleasePrivilege(const AToken: TPrivToken); overload; 164 | begin 165 | if AToken.hProcToken = 0 then exit; 166 | SetPrivilege(AToken.hProcToken, AToken.luid, false); 167 | CloseHandle(AToken.hProcToken); 168 | end; 169 | 170 | 171 | //Allocates a SID for a BUILTIN group. SID has to be freed with FreeSid 172 | function AllocateSidBuiltin(Group: DWORD): PSID; 173 | var SIDAuthNT: SID_IDENTIFIER_AUTHORITY; 174 | begin 175 | SIDAuthNT := SECURITY_NT_AUTHORITY; 176 | if not AllocateAndInitializeSid(@SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, 177 | Group, 0, 0, 0, 0, 0, 0, Result) then 178 | RaiseLastOsError(); 179 | end; 180 | 181 | //Allocates a SID for BUILTIN\Administrators. SID has to be freed with FreeSid. 182 | function AllocateSidBuiltinAdministrators: PSID; 183 | var SIDAuthNT: SID_IDENTIFIER_AUTHORITY; 184 | begin 185 | SIDAuthNT := SECURITY_NT_AUTHORITY; 186 | if not AllocateAndInitializeSid(@SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, 187 | DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, Result) then 188 | RaiseLastOsError(); 189 | end; 190 | 191 | //Returns true if the user is a member of a given builtin group 192 | function IsUserInBuiltinGroup(Group: DWORD): BOOL; 193 | var sidGroup: PSID; 194 | begin 195 | sidGroup := AllocateSidBuiltin(Group); 196 | try 197 | if not CheckTokenMembership(0, sidGroup, Result) then 198 | Result := False; 199 | finally 200 | FreeSid(sidGroup); 201 | end; 202 | end; 203 | 204 | //Returns true if you are currently running with admin privileges 205 | //Under Vista+ will return false when non-elevated. 206 | function IsUserAdmin: BOOL; 207 | var sidAdmin: PSID; 208 | begin 209 | sidAdmin := AllocateSidBuiltinAdministrators; 210 | try 211 | if not CheckTokenMembership(0, sidAdmin, Result) then 212 | Result := False; 213 | finally 214 | FreeSid(sidAdmin); 215 | end; 216 | end; 217 | 218 | 219 | //Replaces ownership for the object 220 | function SetOwnership(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; aNewOwner: PSID): cardinal; 221 | begin 222 | Result := SetNamedSecurityInfo(PChar(AObjectName), AObjectType, OWNER_SECURITY_INFORMATION, 223 | @aNewOwner, nil, nil, nil); 224 | end; 225 | 226 | //Replaces ownership for the object, returns the previous owner or nil if no change was needed. 227 | function SwitchOwnership(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; aNewOwner: PSID; 228 | out aPreviousOwner: PSID; out pDescriptor: PSECURITY_DESCRIPTOR): cardinal; 229 | begin 230 | Log('GetNamedSecurityInfo: '+AObjectName); 231 | Result := GetNamedSecurityInfo(PChar(AObjectName), AObjectType, OWNER_SECURITY_INFORMATION, 232 | @aPreviousOwner, nil, nil, nil, pDescriptor); 233 | if Result <> ERROR_SUCCESS then exit; 234 | 235 | if EqualSid(aPreviousOwner, aNewOwner) then begin 236 | LocalFree(NativeUInt(pDescriptor)); 237 | aPreviousOwner := nil; 238 | Result := ERROR_SUCCESS; 239 | exit; 240 | end; 241 | 242 | Log('SetNamedSecurityInfo: '+AObjectName); 243 | Result := SetNamedSecurityInfo(PChar(AObjectName), AObjectType, OWNER_SECURITY_INFORMATION, 244 | aNewOwner, nil, nil, nil); 245 | end; 246 | 247 | //Adds access permissions for a trustee, if those were not already present. 248 | function AddExplicitPermissions(const AObjectName: string; AObjectType: SE_OBJECT_TYPE; aTrustee: PSID; 249 | APermissions: cardinal; APreviousPermissions: PCardinal): cardinal; 250 | var pDescriptor: PSecurityDescriptor; 251 | pDacl: PACL; 252 | i: integer; 253 | pAce: PACE_HEADER; 254 | pNewDacl: PACL; 255 | pNewAccess: EXPLICIT_ACCESS; 256 | begin 257 | pNewDacl := nil; 258 | pDescriptor := nil; 259 | 260 | Log('GetNamedSecurityInfo: '+AObjectName); 261 | Result := GetNamedSecurityInfo(PChar(AObjectName), AObjectType, DACL_SECURITY_INFORMATION, 262 | nil, nil, @pDacl, nil, pointer(pDescriptor)); 263 | if Result <> ERROR_SUCCESS then exit; 264 | try 265 | Log('Checking entries...'); 266 | for i := 0 to pDacl.AceCount-1 do begin 267 | if not GetAce(pDacl, i, pointer(pAce)) then begin 268 | Result := GetLastError(); //we could continue and try to write anyway, but let's not take risks 269 | exit; 270 | end; 271 | 272 | if pAce.AceType = ACCESS_ALLOWED_ACE_TYPE then 273 | Log('Entry: Type='+IntToStr(pAce.AceType)+', mask='+IntToStr(PACCESS_ALLOWED_ACE(pAce)^.Mask)) 274 | else 275 | Log('Entry: Type='+IntToStr(pAce.AceType)); 276 | 277 | //we only settle on "all required rights in one go" because otherwise it's just too unstable 278 | //we also don't care if there's explicit "deny" or "re-set to less" afterwards, whoever placed it it's their problem 279 | if pAce.AceType <> ACCESS_ALLOWED_ACE_TYPE then continue; 280 | if PACCESS_ALLOWED_ACE(pAce)^.Mask and APermissions <> APermissions then continue; 281 | if not EqualSid(PSID(@PACCESS_ALLOWED_ACE(pAce).SidStart), aTrustee) then continue; 282 | 283 | Log('Existing grant_entry found'); 284 | //Entry exist! 285 | if APreviousPermissions <> nil then 286 | APreviousPermissions^ := PACCESS_ALLOWED_ACE(pAce)^.Mask; 287 | Result := ERROR_SUCCESS; 288 | exit; 289 | end; 290 | 291 | Log('Adding new grant_entry'); 292 | //No granting entry found, add explicitly 293 | pNewAccess.grfAccessPermissions := APermissions; 294 | pNewAccess.grfAccessMode := GRANT_ACCESS; 295 | pNewAccess.grfInheritance := CONTAINER_INHERIT_ACE or OBJECT_INHERIT_ACE; 296 | pNewAccess.Trustee.pMultipleTrustee := nil; 297 | pNewAccess.Trustee.MultipleTrusteeOperation := NO_MULTIPLE_TRUSTEE; 298 | pNewAccess.Trustee.TrusteeForm := TRUSTEE_IS_SID; 299 | pNewAccess.Trustee.TrusteeType := TRUSTEE_IS_UNKNOWN; 300 | pNewAccess.Trustee.ptstrName := PChar(aTrustee); 301 | 302 | Log('SetEntriesInAcl'); 303 | Result := SetEntriesInAcl(1, @pNewAccess, pDacl, pNewDacl); 304 | if Result <> ERROR_SUCCESS then exit; 305 | 306 | Log('SetNamedSecurityInfo'); 307 | Result := SetNamedSecurityInfo(PChar(AObjectName), AObjectType, DACL_SECURITY_INFORMATION, 308 | nil, nil, pNewDacl, nil); 309 | 310 | if APreviousPermissions <> nil then 311 | APreviousPermissions^ := 0; 312 | finally 313 | LocalFree(NativeUInt(pDescriptor)); 314 | if pNewDacl <> nil then 315 | LocalFree(NativeUInt(pNewDacl)); 316 | end; 317 | end; 318 | 319 | end. 320 | -------------------------------------------------------------------------------- /CBSEnum_Main.dfm: -------------------------------------------------------------------------------- 1 | object MainForm: TMainForm 2 | Left = 0 3 | Top = 0 4 | Caption = 'Packages' 5 | ClientHeight = 578 6 | ClientWidth = 686 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | Menu = MainMenu 14 | OldCreateOrder = False 15 | OnCreate = FormCreate 16 | OnDestroy = FormDestroy 17 | OnShow = FormShow 18 | PixelsPerInch = 96 19 | TextHeight = 13 20 | object Panel1: TPanel 21 | Left = 501 22 | Top = 0 23 | Width = 185 24 | Height = 360 25 | Align = alRight 26 | BevelOuter = bvNone 27 | TabOrder = 0 28 | DesignSize = ( 29 | 185 30 | 360) 31 | object Label1: TLabel 32 | Left = 8 33 | Top = 8 34 | Width = 30 35 | Height = 13 36 | Caption = 'Show:' 37 | end 38 | object Label2: TLabel 39 | Left = 6 40 | Top = 106 41 | Width = 33 42 | Height = 13 43 | Caption = 'Group:' 44 | end 45 | object cbShowWOW64: TCheckBox 46 | Left = 6 47 | Top = 50 48 | Width = 171 49 | Height = 17 50 | Anchors = [akLeft, akTop, akRight] 51 | Caption = 'WOW64 versions' 52 | Checked = True 53 | State = cbChecked 54 | TabOrder = 0 55 | OnClick = cbShowWOW64Click 56 | end 57 | object cbShowKb: TCheckBox 58 | Left = 6 59 | Top = 73 60 | Width = 171 61 | Height = 17 62 | Anchors = [akLeft, akTop, akRight] 63 | Caption = 'KB updates' 64 | TabOrder = 1 65 | OnClick = cbShowWOW64Click 66 | end 67 | object pnlGroupMode: TPanel 68 | Left = 0 69 | Top = 125 70 | Width = 185 71 | Height = 62 72 | Anchors = [akLeft, akTop, akRight] 73 | BevelOuter = bvNone 74 | TabOrder = 2 75 | DesignSize = ( 76 | 185 77 | 62) 78 | object rbGroupEachPart: TRadioButton 79 | Left = 6 80 | Top = 0 81 | Width = 171 82 | Height = 17 83 | Anchors = [akLeft, akTop, akRight] 84 | Caption = 'By each part of the name' 85 | TabOrder = 0 86 | OnClick = rbGroupEachPartClick 87 | end 88 | object rbGroupDistinctParts: TRadioButton 89 | Left = 6 90 | Top = 23 91 | Width = 171 92 | Height = 17 93 | Anchors = [akLeft, akTop, akRight] 94 | Caption = 'By distinct parts' 95 | Checked = True 96 | TabOrder = 1 97 | TabStop = True 98 | OnClick = rbGroupEachPartClick 99 | end 100 | object rbGroupFlat: TRadioButton 101 | Left = 6 102 | Top = 46 103 | Width = 171 104 | Height = 17 105 | Anchors = [akLeft, akTop, akRight] 106 | Caption = 'Flat list' 107 | TabOrder = 2 108 | OnClick = rbGroupEachPartClick 109 | end 110 | end 111 | object cbShowHidden: TCheckBox 112 | Left = 6 113 | Top = 27 114 | Width = 171 115 | Height = 17 116 | Anchors = [akLeft, akTop, akRight] 117 | Caption = 'Hidden packages' 118 | Checked = True 119 | State = cbChecked 120 | TabOrder = 3 121 | OnClick = cbShowWOW64Click 122 | end 123 | end 124 | object Panel3: TPanel 125 | Left = 0 126 | Top = 0 127 | Width = 501 128 | Height = 360 129 | Align = alClient 130 | BevelOuter = bvNone 131 | TabOrder = 1 132 | object vtPackages: TVirtualStringTree 133 | Left = 0 134 | Top = 24 135 | Width = 501 136 | Height = 336 137 | Align = alClient 138 | BorderWidth = 1 139 | Header.AutoSizeIndex = 0 140 | Header.Font.Charset = DEFAULT_CHARSET 141 | Header.Font.Color = clWindowText 142 | Header.Font.Height = -11 143 | Header.Font.Name = 'Tahoma' 144 | Header.Font.Style = [] 145 | Header.MainColumn = -1 146 | PopupMenu = PopupMenu 147 | TabOrder = 0 148 | TreeOptions.MiscOptions = [toFullRepaintOnResize, toInitOnSave, toToggleOnDblClick, toWheelPanning, toEditOnClick] 149 | TreeOptions.PaintOptions = [toHideFocusRect, toShowButtons, toShowDropmark, toShowRoot, toShowTreeLines, toThemeAware, toUseBlendedImages] 150 | TreeOptions.SelectionOptions = [toFullRowSelect, toMultiSelect, toRightClickSelect] 151 | OnFocusChanged = vtPackagesFocusChanged 152 | OnFreeNode = vtPackagesFreeNode 153 | OnGetText = vtPackagesGetText 154 | OnPaintText = vtPackagesPaintText 155 | OnGetNodeDataSize = vtPackagesGetNodeDataSize 156 | OnInitNode = vtPackagesInitNode 157 | Columns = <> 158 | end 159 | object edtFilter: TEdit 160 | Left = 0 161 | Top = 0 162 | Width = 501 163 | Height = 24 164 | Align = alTop 165 | BevelInner = bvNone 166 | Font.Charset = DEFAULT_CHARSET 167 | Font.Color = clWindowText 168 | Font.Height = -13 169 | Font.Name = 'Tahoma' 170 | Font.Style = [] 171 | ParentFont = False 172 | TabOrder = 1 173 | OnChange = edtFilterChange 174 | OnKeyDown = edtFilterKeyDown 175 | end 176 | end 177 | object pcPageInfo: TPageControl 178 | Left = 0 179 | Top = 360 180 | Width = 686 181 | Height = 218 182 | ActivePage = tsInfo 183 | Align = alBottom 184 | TabOrder = 2 185 | object tsInfo: TTabSheet 186 | Caption = 'Info' 187 | OnEnter = tsInfoEnter 188 | DesignSize = ( 189 | 678 190 | 190) 191 | object lblDescription: TLabel 192 | Left = 6 193 | Top = 3 194 | Width = 669 195 | Height = 54 196 | Anchors = [akLeft, akTop, akRight] 197 | AutoSize = False 198 | ExplicitWidth = 585 199 | end 200 | object lbUpdates: TListBox 201 | Left = 3 202 | Top = 63 203 | Width = 671 204 | Height = 97 205 | Anchors = [akLeft, akTop, akRight] 206 | ItemHeight = 13 207 | TabOrder = 0 208 | end 209 | end 210 | object tsResources: TTabSheet 211 | Caption = 'Resources' 212 | ImageIndex = 1 213 | OnEnter = tsResourcesEnter 214 | end 215 | end 216 | object PopupMenu: TPopupMenu 217 | OnPopup = PopupMenuPopup 218 | Left = 144 219 | Top = 16 220 | object pmCopySubmenu: TMenuItem 221 | Caption = 'Copy' 222 | object pmCopyPackageNames: TMenuItem 223 | Caption = 'Package names' 224 | Hint = 'Copy all selected package names into clipboard' 225 | ShortCut = 16451 226 | OnClick = pmCopyPackageNamesClick 227 | end 228 | object pmCopyUninstallationCommands: TMenuItem 229 | Caption = 'Uninstallation commands' 230 | Hint = 231 | 'Copy DISM command for uninstalling all selected packages into cl' + 232 | 'ipboard' 233 | OnClick = pmCopyUninstallationCommandsClick 234 | end 235 | end 236 | object Saveselectedpackagelist1: TMenuItem 237 | Caption = 'Save selected package list...' 238 | OnClick = Saveselectedpackagelist1Click 239 | end 240 | object pmVisibility: TMenuItem 241 | Caption = 'Visibility' 242 | object pmMakeVisible: TMenuItem 243 | Caption = 'Make visible' 244 | OnClick = pmMakeVisibleClick 245 | end 246 | object pmMakeInvisible: TMenuItem 247 | Caption = 'Make invisible' 248 | OnClick = pmMakeInvisibleClick 249 | end 250 | object pmRestoreDefaultVisibility: TMenuItem 251 | Caption = 'Restore default visibility' 252 | OnClick = pmRestoreDefaultVisibilityClick 253 | end 254 | end 255 | object pmManageSubmenu: TMenuItem 256 | Caption = 'Manage' 257 | object pmDecouplePackages: TMenuItem 258 | Caption = 'Decouple' 259 | OnClick = pmDecouplePackagesClick 260 | end 261 | end 262 | object pmUninstall: TMenuItem 263 | Caption = 'Uninstall' 264 | Hint = 'Uninstall selected package' 265 | OnClick = pmUninstallAllClick 266 | end 267 | object pmUninstallAll: TMenuItem 268 | Caption = 'Uninstall all' 269 | Hint = 'Uninstall all selected packages' 270 | OnClick = pmUninstallAllClick 271 | end 272 | object N1: TMenuItem 273 | Caption = '-' 274 | end 275 | object pmReload: TMenuItem 276 | Caption = 'Reload' 277 | OnClick = pmReloadClick 278 | end 279 | end 280 | object ImageList1: TImageList 281 | Left = 80 282 | Top = 16 283 | end 284 | object MainMenu: TMainMenu 285 | Left = 16 286 | Top = 16 287 | object File1: TMenuItem 288 | Caption = 'File' 289 | object pmSavePackageList: TMenuItem 290 | Caption = 'Save package list...' 291 | OnClick = pmSavePackageListClick 292 | end 293 | object pmUninstallByList: TMenuItem 294 | Caption = 'Uninstall by list...' 295 | OnClick = pmUninstallByListClick 296 | end 297 | object N2: TMenuItem 298 | Caption = '-' 299 | end 300 | object Exit1: TMenuItem 301 | Caption = 'Exit' 302 | OnClick = Exit1Click 303 | end 304 | end 305 | object Edit1: TMenuItem 306 | Caption = 'Edit' 307 | object pmTakeRegistryOwnership: TMenuItem 308 | Caption = 'Take registry ownership' 309 | Hint = 'Take ownership of CBS registry key in a safe manner' 310 | OnClick = pmTakeRegistryOwnershipClick 311 | end 312 | object pmDecoupleAllPackages: TMenuItem 313 | Caption = 'Decouple all packages' 314 | Hint = 315 | 'Decouple all packages from their configured parents so that they' + 316 | ' can be uninstalled separately from the whole system' 317 | OnClick = pmDecoupleAllPackagesClick 318 | end 319 | object N3: TMenuItem 320 | Caption = '-' 321 | end 322 | object pmMakeAllVisibile: TMenuItem 323 | Caption = 'Make all visible' 324 | OnClick = pmMakeAllVisibileClick 325 | end 326 | object pmMakeAllInvisible: TMenuItem 327 | Caption = 'Make all invisible' 328 | OnClick = pmMakeAllInvisibleClick 329 | end 330 | object pmRestoreDefaltVisibilityAll: TMenuItem 331 | Caption = 'Restore default visibility for all' 332 | OnClick = pmRestoreDefaltVisibilityAllClick 333 | end 334 | object N4: TMenuItem 335 | Caption = '-' 336 | end 337 | object Rebuildassemblydatabase1: TMenuItem 338 | Caption = 'Rebuild assembly database' 339 | OnClick = Rebuildassemblydatabase1Click 340 | end 341 | end 342 | object Service1: TMenuItem 343 | Caption = 'Service' 344 | object pmOpenCBSRegistry: TMenuItem 345 | Caption = 'Open CBS registry...' 346 | Hint = 'Open registry editor at a CBS key' 347 | OnClick = pmOpenCBSRegistryClick 348 | end 349 | object Diskcleanup1: TMenuItem 350 | Caption = 'Disk cleanup...' 351 | Hint = 'Run disk cleanup utility' 352 | OnClick = Diskcleanup1Click 353 | end 354 | object DismCleanup1: TMenuItem 355 | Caption = 'DISM image cleanup...' 356 | OnClick = DismCleanup1Click 357 | end 358 | object Optionalfeatures1: TMenuItem 359 | Caption = 'Optional features...' 360 | Hint = 'Enable or disalbe optional Windows features' 361 | OnClick = Optionalfeatures1Click 362 | end 363 | end 364 | end 365 | object UninstallListOpenDialog: TOpenDialog 366 | Filter = 'All files (*.*)|*.*' 367 | Title = 'Select uninstall list' 368 | Left = 56 369 | Top = 72 370 | end 371 | object PackageListSaveDialog: TSaveTextFileDialog 372 | DefaultExt = '*.*' 373 | Filter = 'Text files (*.txt)|*.txt|All files (*.*)|*.*' 374 | Title = 'Save package list' 375 | Left = 56 376 | Top = 136 377 | end 378 | end 379 | -------------------------------------------------------------------------------- /CBSEnum.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {111B137F-7161-4FF6-A8FC-19B75BE726D6} 4 | 14.3 5 | VCL 6 | CBSEnum.dpr 7 | True 8 | Debug 9 | Win64 10 | 3 11 | Application 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Cfg_1 40 | true 41 | true 42 | 43 | 44 | true 45 | Base 46 | true 47 | 48 | 49 | ..\ManifestEnum;$(SHARE)\Lib\sqlite3;..\ManifestEnum\Views;..\ManifestEnum\Db;$(DCC_UnitSearchPath) 50 | None 51 | CompanyName=;FileDescription=CBSEnum tool;FileVersion=0.9.0.0;InternalName=;LegalCopyright=me@boku.ru;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=A tool to view and manage CBS packages on Windows 52 | true 53 | 9 54 | 0 55 | 1049 56 | CBSEnum_Icon.ico 57 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 58 | .\$(Platform)\$(Config) 59 | .\$(Platform)\$(Config) 60 | false 61 | false 62 | false 63 | false 64 | false 65 | 66 | 67 | $(BDS)\bin\default_app.manifest 68 | JvGlobus;JvMM;JvManagedThreads;DBXSqliteDriver;JvDlgs;IndySystem;JvCrypt;StubPanelPkgD17;vclib;inetdbbde;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;JvNet;DataSnapProviderClient;JvDotNetCtrls;DBXSybaseASEDriver;DbxCommonDriver;vclimg;VirtualTreesD14;dbxcds;JaletControls;MetropolisUILiveTile;JvXPCtrls;vcldb;vcldsnap;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;JvCore;vclribbon;dsnap;IndyIPServer;IndyCore;vcl;IndyIPCommon;CloudService;DBXMSSQLDriver;JvAppFrm;JvDB;JvRuntimeDesign;inetdbxpress;webdsnap;JclDeveloperTools;JvDocking;adortl;JvWizards;JvHMI;bindcompfmx;JvBands;vcldbx;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;JvPluginSystem;JclContainers;DBXOdbcDriver;JvCmp;JvSystem;xmlrtl;svnui;ibxpress;JvTimeFramework;JvControls;IndyProtocols;DBXMySQLDriver;vclactnband;bindengine;soaprtl;bindcompdbx;JvJans;JvPageComps;bindcompvcl;JvStdCtrls;JvCustom;Jcl;vclie;JvPrintPreview;vcltouch;websnap;VclSmp;DBXInformixDriver;DataSnapConnectors;dsnapcon;DBXFirebirdDriver;inet;JclVcl;JvPascalInterpreter;vclx;svn;DBXSybaseASADriver;bdertl;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 69 | true 70 | 1033 71 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 72 | 73 | 74 | $(BDS)\bin\default_app.manifest 75 | true 76 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 77 | 1033 78 | DBXSqliteDriver;IndySystem;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;vcldb;vcldsnap;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;dsnap;IndyIPServer;IndyCore;vcl;IndyIPCommon;DBXMSSQLDriver;inetdbxpress;webdsnap;adortl;bindcompfmx;rtl;dbrtl;DbxClientDriver;bindcomp;inetdb;DBXOdbcDriver;xmlrtl;IndyProtocols;DBXMySQLDriver;vclactnband;bindengine;soaprtl;bindcompdbx;bindcompvcl;vclie;vcltouch;websnap;VclSmp;DBXInformixDriver;dsnapcon;DBXFirebirdDriver;inet;vclx;DBXSybaseASADriver;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 79 | 80 | 81 | DEBUG;$(DCC_Define) 82 | true 83 | false 84 | true 85 | true 86 | true 87 | 88 | 89 | false 90 | 91 | 92 | $(BDS)\bin\default_app.manifest 93 | 1033 94 | 95 | 96 | false 97 | RELEASE;$(DCC_Define) 98 | 0 99 | false 100 | 101 | 102 | 103 | MainSource 104 | 105 | 106 |
MainForm
107 | dfm 108 |
109 | 110 |
JobProcessorForm
111 |
112 | 113 | 114 |
ResourceModule
115 |
116 | 117 | 118 | 119 | 120 | 121 | Cfg_2 122 | Base 123 | 124 | 125 | Base 126 | 127 | 128 | Cfg_1 129 | Base 130 | 131 |
132 | 133 | Delphi.Personality.12 134 | 135 | 136 | 137 | 138 | False 139 | False 140 | 1 141 | 0 142 | 0 143 | 0 144 | False 145 | False 146 | False 147 | False 148 | False 149 | 1049 150 | 1251 151 | 152 | 153 | 154 | 155 | 1.0.0.0 156 | 157 | 158 | 159 | 160 | 161 | 1.0.0.0 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | CBSEnum.dpr 174 | 175 | 176 | Microsoft Office 2000 Sample Automation Server Wrapper Components 177 | Microsoft Office XP Sample Automation Server Wrapper Components 178 | Embarcadero C++Builder Office 2000 Servers Package 179 | Embarcadero C++Builder Office XP Servers Package 180 | 181 | 182 | 183 | 184 | True 185 | True 186 | 187 | 188 | 12 189 | 190 | 191 | 192 |
193 | -------------------------------------------------------------------------------- /CBSEnum_Main.pas: -------------------------------------------------------------------------------- 1 | unit CBSEnum_Main; 2 | 3 | interface 4 | 5 | uses 6 | Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Menus, 7 | StdCtrls, ExtCtrls, ImgList, ComCtrls, ExtDlgs, VirtualTrees, Generics.Collections, Registry, 8 | UniStrUtils, AssemblyDb, AssemblyDb.Assemblies, AssemblyResourcesView; 9 | 10 | type 11 | TPackage = class 12 | Name: string; //full package name 13 | DisplayName: string; //display name 14 | Variation: string; //base/WOW64 15 | CbsVisibility: integer; 16 | DefaultCbsVisibility: integer; //if preserved in DefVis key by anyone 17 | end; 18 | 19 | TPackageArray = array of TPackage; 20 | PPackageArray = ^TPackageArray; 21 | 22 | TPackageGroup = class 23 | protected 24 | Name: string; 25 | Subgroups: TObjectList; 26 | Packages: TObjectList; 27 | public 28 | constructor Create; 29 | destructor Destroy; override; 30 | procedure AddPackage(APackageName: string; APackage: TPackage); overload; 31 | function AddPackage(AFullPackageName: string): TPackage; overload; 32 | function FindSubgroup(const AGroupName: string): TPackageGroup; 33 | function NeedSubgroup(const AGroupName: string): TPackageGroup; 34 | procedure CompactNames; 35 | function SelectMatching(const AMask: string): TPackageArray; overload; 36 | procedure SelectMatching(AMask: string; var AArray: TPackageArray); overload; 37 | end; 38 | 39 | TNdPackageData = record 40 | DisplayName: string; //display name 41 | Package: TPackage; //if assigned 42 | IsVisible: boolean; 43 | end; 44 | PNdPackageData = ^TNdPackageData; 45 | 46 | TMainForm = class(TForm) 47 | Panel1: TPanel; 48 | Label1: TLabel; 49 | cbShowWOW64: TCheckBox; 50 | PopupMenu: TPopupMenu; 51 | cbShowKb: TCheckBox; 52 | pmUninstall: TMenuItem; 53 | N1: TMenuItem; 54 | pmReload: TMenuItem; 55 | Label2: TLabel; 56 | pnlGroupMode: TPanel; 57 | rbGroupEachPart: TRadioButton; 58 | rbGroupDistinctParts: TRadioButton; 59 | Panel3: TPanel; 60 | vtPackages: TVirtualStringTree; 61 | edtFilter: TEdit; 62 | pmUninstallAll: TMenuItem; 63 | ImageList1: TImageList; 64 | pmCopyPackageNames: TMenuItem; 65 | pcPageInfo: TPageControl; 66 | tsInfo: TTabSheet; 67 | lblDescription: TLabel; 68 | lbUpdates: TListBox; 69 | MainMenu: TMainMenu; 70 | File1: TMenuItem; 71 | Service1: TMenuItem; 72 | Diskcleanup1: TMenuItem; 73 | Exit1: TMenuItem; 74 | Optionalfeatures1: TMenuItem; 75 | pmCopyUninstallationCommands: TMenuItem; 76 | pmOpenCBSRegistry: TMenuItem; 77 | Edit1: TMenuItem; 78 | rbGroupFlat: TRadioButton; 79 | pmMakeVisible: TMenuItem; 80 | pmMakeInvisible: TMenuItem; 81 | pmRestoreDefaultVisibility: TMenuItem; 82 | pmVisibility: TMenuItem; 83 | pmMakeAllVisibile: TMenuItem; 84 | pmMakeAllInvisible: TMenuItem; 85 | pmRestoreDefaltVisibilityAll: TMenuItem; 86 | cbShowHidden: TCheckBox; 87 | pmUninstallByList: TMenuItem; 88 | N2: TMenuItem; 89 | UninstallListOpenDialog: TOpenDialog; 90 | pmSavePackageList: TMenuItem; 91 | PackageListSaveDialog: TSaveTextFileDialog; 92 | Saveselectedpackagelist1: TMenuItem; 93 | pmTakeRegistryOwnership: TMenuItem; 94 | pmDecoupleAllPackages: TMenuItem; 95 | pmDecouplePackages: TMenuItem; 96 | N3: TMenuItem; 97 | pmCopySubmenu: TMenuItem; 98 | pmManageSubmenu: TMenuItem; 99 | N4: TMenuItem; 100 | Rebuildassemblydatabase1: TMenuItem; 101 | DismCleanup1: TMenuItem; 102 | tsResources: TTabSheet; 103 | procedure FormShow(Sender: TObject); 104 | procedure vtPackagesGetNodeDataSize(Sender: TBaseVirtualTree; 105 | var NodeDataSize: Integer); 106 | procedure vtPackagesFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); 107 | procedure vtPackagesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; 108 | Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); 109 | procedure vtPackagesInitNode(Sender: TBaseVirtualTree; ParentNode, 110 | Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); 111 | procedure cbShowWOW64Click(Sender: TObject); 112 | procedure vtPackagesPaintText(Sender: TBaseVirtualTree; 113 | const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; 114 | TextType: TVSTTextType); 115 | procedure PopupMenuPopup(Sender: TObject); 116 | procedure pmReloadClick(Sender: TObject); 117 | procedure rbGroupEachPartClick(Sender: TObject); 118 | procedure edtFilterChange(Sender: TObject); 119 | procedure edtFilterKeyDown(Sender: TObject; var Key: Word; 120 | Shift: TShiftState); 121 | procedure pmUninstallAllClick(Sender: TObject); 122 | procedure pmCopyPackageNamesClick(Sender: TObject); 123 | procedure vtPackagesFocusChanged(Sender: TBaseVirtualTree; 124 | Node: PVirtualNode; Column: TColumnIndex); 125 | procedure tsInfoEnter(Sender: TObject); 126 | procedure Exit1Click(Sender: TObject); 127 | procedure Diskcleanup1Click(Sender: TObject); 128 | procedure Optionalfeatures1Click(Sender: TObject); 129 | procedure pmCopyUninstallationCommandsClick(Sender: TObject); 130 | procedure pmOpenCBSRegistryClick(Sender: TObject); 131 | procedure pmMakeVisibleClick(Sender: TObject); 132 | procedure pmMakeInvisibleClick(Sender: TObject); 133 | procedure pmRestoreDefaultVisibilityClick(Sender: TObject); 134 | procedure pmMakeAllVisibileClick(Sender: TObject); 135 | procedure pmMakeAllInvisibleClick(Sender: TObject); 136 | procedure pmRestoreDefaltVisibilityAllClick(Sender: TObject); 137 | procedure pmUninstallByListClick(Sender: TObject); 138 | procedure Saveselectedpackagelist1Click(Sender: TObject); 139 | procedure pmSavePackageListClick(Sender: TObject); 140 | procedure pmDecoupleAllPackagesClick(Sender: TObject); 141 | procedure pmDecouplePackagesClick(Sender: TObject); 142 | procedure pmTakeRegistryOwnershipClick(Sender: TObject); 143 | procedure FormCreate(Sender: TObject); 144 | procedure FormDestroy(Sender: TObject); 145 | procedure Rebuildassemblydatabase1Click(Sender: TObject); 146 | procedure DismCleanup1Click(Sender: TObject); 147 | procedure tsResourcesEnter(Sender: TObject); 148 | protected 149 | FPackages: TPackageGroup; 150 | FTotalPackages: integer; 151 | 152 | protected 153 | FVisiblePackages: integer; 154 | procedure ReloadPackageTree(AGroup: TPackageGroup; ATreeNode: PVirtualNode); 155 | function CreateNode(AParent: PVirtualNode; ADisplayName: string; APackage: TPackage): PVirtualNode; 156 | function FindNode(AParent: PVirtualNode; ADisplayName: string): PVirtualNode; 157 | procedure UpdateNodeVisibility(); 158 | procedure ResetVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); 159 | procedure UpdatePackageVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); 160 | procedure ApplyVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); 161 | procedure CountVisiblePackages_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); 162 | function IsPackageNodeVisible(ANode: PVirtualNode): boolean; 163 | protected 164 | function GetAllPackages: TPackageArray; 165 | function GetSelectedPackages: TPackageArray; 166 | function GetChildPackages(ANode: PVirtualNode): TPackageArray; 167 | procedure GetPackages_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; 168 | Data: Pointer; var Abort: Boolean); 169 | function GetSelectedPackageNames: TStringArray; 170 | function GetChildPackageNames(ANode: PVirtualNode): TStringArray; 171 | 172 | protected 173 | procedure DismUninstall(const APackageName: string); overload; 174 | procedure DismUninstall(const APackageNames: TStringArray); overload; 175 | procedure SetCbsVisibility(const AKey: string; APackages: TPackageArray; AVisibility: integer); overload; 176 | procedure SetCbsVisibility(APackages: TPackageArray; AVisibility: integer); overload; 177 | procedure SavePackageList(APackageNames: TStringArray); 178 | 179 | protected //Assembly database 180 | FDb: TAssemblyDb; 181 | FResources: TAssemblyResourcesForm; 182 | 183 | protected 184 | procedure UpdateFormCaption; 185 | public 186 | procedure Reload; 187 | 188 | end; 189 | 190 | var 191 | MainForm: TMainForm; 192 | 193 | const 194 | hkCbsRoot = HKEY_LOCAL_MACHINE; 195 | sCbsRootSec = 'MACHINE'; //for security-related functions 196 | sCbsKey = 'Software\Microsoft\Windows\CurrentVersion\Component Based Servicing'; 197 | 198 | const 199 | CBS_E_INVALID_PACKAGE = $800F0805; 200 | 201 | function IsPackageInList(const AList: TPackageArray; APackage: TPackage): boolean; inline; overload; 202 | function IsPackageInList(const AList: TStringArray; APackageName: string): boolean; inline; overload; 203 | 204 | implementation 205 | uses Clipbrd, XmlDoc, XmlIntf, AccCtrl, OsUtils, AclHelpers, CBSEnum_JobProcessor, TakeOwnershipJob, 206 | DecouplePackagesJob, FilenameUtils, AssemblyDbBuilder, Generics.Defaults, WildcardMatching; 207 | 208 | {$R *.dfm} 209 | 210 | resourcestring 211 | sCannotOpenCbsRegistry = 'Cannot open registry key for packages. Perpahs ' 212 | +'you''re not running the app with administrator rights? Or the Windows ' 213 | +'version is incompatible.'; 214 | 215 | 216 | function IsPackageInList(const AList: TPackageArray; APackage: TPackage): boolean; inline; overload; 217 | var i: integer; 218 | begin 219 | Result := false; 220 | for i := 0 to Length(AList)-1 do 221 | if AList[i]=APackage then begin 222 | Result := true; 223 | break; 224 | end; 225 | end; 226 | 227 | function IsPackageInList(const AList: TStringArray; APackageName: string): boolean; inline; overload; 228 | var i: integer; 229 | begin 230 | Result := false; 231 | for i := 0 to Length(AList)-1 do 232 | if SameText(AList[i], APackageName) then begin 233 | Result := true; 234 | break; 235 | end; 236 | end; 237 | 238 | function PackagesToPackageNames(APackages: TPackageArray): TStringArray; 239 | var i: integer; 240 | begin 241 | SetLength(Result, Length(APackages)); 242 | for i := 0 to Length(APackages)-1 do 243 | Result[i] := APackages[i].Name; 244 | end; 245 | 246 | 247 | constructor TPackageGroup.Create; 248 | begin 249 | inherited; 250 | Subgroups := TObjectList.Create; 251 | Packages := TObjectList.Create; 252 | end; 253 | 254 | destructor TPackageGroup.Destroy; 255 | begin 256 | FreeAndNil(Packages); 257 | FreeAndNil(Subgroups); 258 | inherited; 259 | end; 260 | 261 | //Mostly used internally to route package to appropriate group 262 | procedure TPackageGroup.AddPackage(APackageName: string; APackage: TPackage); 263 | var pos_minus, pos_tilde: integer; 264 | AGroupName: string; 265 | AGroup: TPackageGroup; 266 | begin 267 | //Eat one part of the name. Mind names like Microsoft-Windows-Defender~ru-RU (note last minus) 268 | pos_tilde := pos('~', APackageName); 269 | repeat 270 | pos_minus := pos('-', APackageName); 271 | if (pos_minus <= 0) or ((pos_tilde > 0) and (pos_tilde < pos_minus)) then 272 | break; //last part 273 | 274 | AGroupName := copy(APackageName, 1, pos_minus-1); 275 | delete(APackageName, 1, pos_minus); 276 | pos_tilde := pos_tilde - pos_minus; //since we've eaten some chars 277 | 278 | if SameText(AGroupName, 'WOW64') then begin 279 | APackage.Variation := 'WOW64'; 280 | APackageName := APackageName + ' (WOW64)'; //could be made properly, on display 281 | continue; //chew another part 282 | end; 283 | 284 | AGroup := Self.NeedSubgroup(AGroupName); 285 | AGroup.AddPackage(APackageName, APackage); 286 | exit; 287 | until false; 288 | 289 | //Most package names end with -Package. Ignore this. 290 | if (pos_tilde > 0) and SameText(copy(APackageName, 1, pos_tilde-1), 'Package') then 291 | delete(APackageName, 1, pos_tilde); 292 | APackage.DisplayName := APackageName; 293 | Packages.Add(APackage); 294 | end; 295 | 296 | //Mostly call this from outside 297 | function TPackageGroup.AddPackage(AFullPackageName: string): TPackage; 298 | begin 299 | Result := TPackage.Create; 300 | Result.Name := AFullPackageName; 301 | Self.AddPackage(AFullPackageName, Result); 302 | end; 303 | 304 | function TPackageGroup.FindSubgroup(const AGroupName: string): TPackageGroup; 305 | var group: TPackageGroup; 306 | begin 307 | Result := nil; 308 | for group in Subgroups do 309 | if SameText(group.Name, AGroupName) then begin 310 | Result := group; 311 | break; 312 | end; 313 | end; 314 | 315 | function TPackageGroup.NeedSubgroup(const AGroupName: string): TPackageGroup; 316 | begin 317 | Result := FindSubgroup(AGroupName); 318 | if Result = nil then begin 319 | Result := TPackageGroup.Create; 320 | Result.Name := AGroupName; 321 | Self.Subgroups.Add(Result); 322 | end; 323 | end; 324 | 325 | procedure TPackageGroup.CompactNames; 326 | var group: TPackageGroup; 327 | subpkg: TPackage; 328 | i: integer; 329 | begin 330 | for i := Subgroups.Count-1 downto 0 do begin 331 | Subgroups[i].CompactNames; 332 | if (Subgroups[i].Packages.Count=1) and (Subgroups[i].Subgroups.Count=0) then begin 333 | subpkg := Subgroups[i].Packages[0]; 334 | subpkg.DisplayName := Subgroups[i].Name + '-' + subpkg.DisplayName; 335 | Self.Packages.Add(subpkg); 336 | Subgroups[i].Packages.Extract(subpkg); 337 | Self.Subgroups.Remove(Subgroups[i]); 338 | end; 339 | end; 340 | 341 | if (Self.Subgroups.Count = 1) and (Self.Packages.Count = 0) then begin 342 | group := Self.Subgroups[0]; 343 | Self.Subgroups.Extract(group); 344 | for i := group.Subgroups.Count-1 downto 0 do begin 345 | Self.Subgroups.Add(group.Subgroups[i]); 346 | group.Subgroups.Extract(group.Subgroups[i]); 347 | end; 348 | for i := group.Packages.Count-1 downto 0 do begin 349 | Self.Packages.Add(group.Packages[i]); 350 | group.Packages.Extract(group.Packages[i]); 351 | end; 352 | Self.Name := Self.Name + '-' + group.Name; 353 | FreeAndNil(group); 354 | end; 355 | end; 356 | 357 | function TPackageGroup.SelectMatching(const AMask: string): TPackageArray; 358 | begin 359 | SetLength(Result, 0); 360 | SelectMatching(AMask, Result); 361 | end; 362 | 363 | procedure TPackageGroup.SelectMatching(AMask: string; var AArray: TPackageArray); 364 | var i: integer; 365 | begin 366 | AMask := AMask.ToLower; 367 | for i := 0 to Packages.Count-1 do 368 | if WildcardMatchCase(PChar(Packages[i].Name.ToLower), PChar(AMask)) 369 | and not IsPackageInList(AArray, Packages[i]) then begin 370 | SetLength(AArray, Length(AArray)+1); 371 | AArray[Length(AArray)-1] := Packages[i]; 372 | end; 373 | 374 | for i := 0 to Subgroups.Count-1 do 375 | Subgroups[i].SelectMatching(AMask, AArray); 376 | end; 377 | 378 | 379 | 380 | //Sets Visibility parameter for all packages from the list where applicable. 381 | //Preserves old value in DefVis if none yet preserved (like other tools do). 382 | //Skips packages where no changes are needed. 383 | //-1 is a special value meaning "restore to DefVis". 384 | procedure TMainForm.SetCbsVisibility(const AKey: string; APackages: TPackageArray; AVisibility: integer); 385 | var package: TPackage; 386 | reg: TRegistry; 387 | curDefVis: integer; 388 | begin 389 | reg := TRegistry.Create; 390 | try 391 | reg.RootKey := hkCbsRoot; 392 | for package in APackages do try 393 | if (AVisibility >= 0) and (package.CbsVisibility = AVisibility) then 394 | continue; //nothing to change 395 | if (AVisibility < 0) and (package.CbsVisibility = package.DefaultCbsVisibility) then 396 | continue; 397 | //And there's no point in querying the registry again. Even if someone 398 | //changed it in the background, just Refresh() and do this again. 399 | 400 | //We want to store DefVis once, and then never touch it because it contains 401 | //original value, whatever changes happen later 402 | if not reg.OpenKey(AKey+'\'+Package.Name, false) then exit; //no key, whatever 403 | try 404 | curDefVis := reg.ReadInteger('DefVis'); 405 | except 406 | //Only write if we can't read, no key. Otherwise leave alone 407 | on E: ERegistryException do begin 408 | //if there's no key then we set DefaultCbsVisibility to CbsVisibility on load 409 | reg.WriteInteger('DefVis', package.DefaultCbsVisibility); 410 | curDefVis := package.DefaultCbsVisibility; 411 | end; 412 | end; 413 | 414 | package.DefaultCbsVisibility := curDefVis; //we've read it anyway 415 | if AVisibility >= 0 then 416 | package.CbsVisibility := AVisibility 417 | else 418 | package.CbsVisibility := package.DefaultCbsVisibility; 419 | reg.WriteInteger('Visibility', package.CbsVisibility); 420 | reg.CloseKey; 421 | except 422 | on E: ERegistryException do begin 423 | reg.CloseKey; 424 | if MessageBox(Self.Handle, 425 | PChar('Cannot process package '+Package.Name+':'#13+E.Message+'. ' 426 | +'Continue with other packages nevertheless?'), 427 | PChar('Error'), MB_ICONERROR+MB_YESNO) <> ID_YES then break; 428 | //else continue, whatever 429 | end; 430 | end; 431 | 432 | finally 433 | FreeAndNil(reg); 434 | end; 435 | end; 436 | 437 | procedure TMainForm.SetCbsVisibility(APackages: TPackageArray; AVisibility: integer); 438 | begin 439 | SetCbsVisibility(sCbsKey+'\Packages', APackages, AVisibility); 440 | SetCbsVisibility(sCbsKey+'\PackagesPending', APackages, AVisibility); 441 | 442 | UpdateNodeVisibility(); //update everywhere because it's easier than figuring out who's whose parent and whatnot 443 | vtPackages.InvalidateChildren(nil, false); 444 | vtPackages.Repaint; 445 | end; 446 | 447 | 448 | 449 | procedure TMainForm.FormCreate(Sender: TObject); 450 | begin 451 | FDb := TAssemblyDb.Create; 452 | FDb.Open(AppFolder+'\assembly.db'); 453 | FResources := TAssemblyResourcesForm.Create(Self); 454 | FResources.ShowDependencies := true; 455 | FResources.Db := FDb; 456 | FResources.ManualDock(tsResources, tsResources, alClient); 457 | FResources.Align := alClient; 458 | FResources.Visible := true; 459 | end; 460 | 461 | procedure TMainForm.FormDestroy(Sender: TObject); 462 | begin 463 | FreeAndNil(FResources); 464 | FreeAndNil(FDb); 465 | end; 466 | 467 | procedure TMainForm.FormShow(Sender: TObject); 468 | begin 469 | Reload; 470 | end; 471 | 472 | procedure TMainForm.Reload; 473 | var reg: TRegistry; 474 | packages: TStringList; 475 | package: TPackage; 476 | i: integer; 477 | begin 478 | FreeAndNil(FPackages); 479 | FPackages := TPackageGroup.Create; 480 | FTotalPackages := 0; 481 | vtPackages.Clear; 482 | FVisiblePackages := 0; //will be set in first UpdateNodeVisibility 483 | 484 | reg := TRegistry.Create; 485 | packages := TStringList.Create; 486 | try 487 | reg.RootKey := HKEY_LOCAL_MACHINE; 488 | reg.Access := KEY_READ; 489 | if not reg.OpenKey(sCbsKey+'\Packages', false) then 490 | raise Exception.Create(sCannotOpenCbsRegistry); 491 | reg.GetKeyNames(packages); 492 | FTotalPackages := packages.Count; 493 | for i := 0 to packages.Count-1 do begin 494 | if rbGroupFlat.Checked then begin 495 | package := TPackage.Create; 496 | package.Name := packages[i]; 497 | package.DisplayName := packages[i]; 498 | FPackages.Packages.Add(package); 499 | end else 500 | package := FPackages.AddPackage(packages[i]); 501 | reg.CloseKey; 502 | if not reg.OpenKey(sCbsKey+'\Packages\'+packages[i], false) then 503 | continue; //because whatever 504 | try 505 | package.CbsVisibility := reg.ReadInteger('Visibility'); 506 | except 507 | on E: ERegistryException do //don't break on each missing property 508 | package.CbsVisibility := 1; //assume visible 509 | end; 510 | try 511 | package.DefaultCbsVisibility := reg.ReadInteger('DefVis'); 512 | except 513 | on E: ERegistryException do 514 | package.DefaultCbsVisibility := package.CbsVisibility; //assume current 515 | end; 516 | end; 517 | 518 | finally 519 | FreeAndNil(reg); 520 | end; 521 | 522 | if rbGroupDistinctParts.Checked then 523 | FPackages.CompactNames; 524 | 525 | vtPackages.BeginUpdate; 526 | try 527 | ReloadPackageTree(FPackages, nil); 528 | if rbGroupFlat.Checked then 529 | vtPackages.TreeOptions.PaintOptions := vtPackages.TreeOptions.PaintOptions - [toShowRoot] 530 | else 531 | vtPackages.TreeOptions.PaintOptions := vtPackages.TreeOptions.PaintOptions + [toShowRoot]; 532 | finally 533 | vtPackages.EndUpdate; 534 | end; 535 | 536 | //Have to do it again because just setting Node visibility one by one doesn't 537 | //take care of parent nodes becoming visible as needed when child nodes are 538 | //visible. 539 | UpdateNodeVisibility; 540 | end; 541 | 542 | procedure TMainForm.UpdateFormCaption; 543 | begin 544 | Self.Caption := IntToStr(FVisiblePackages) + ' packages ('+IntToStr(FTotalPackages) + ' total)'; 545 | end; 546 | 547 | procedure TMainForm.ReloadPackageTree(AGroup: TPackageGroup; ATreeNode: PVirtualNode); 548 | var group: TPackageGroup; 549 | pkg: TPackage; 550 | node: PVirtualNode; 551 | begin 552 | for group in AGroup.Subgroups do begin 553 | node := CreateNode(ATreeNode, group.Name, nil); 554 | ReloadPackageTree(group, node); 555 | end; 556 | for pkg in AGroup.Packages do 557 | CreateNode(ATreeNode, pkg.DisplayName, pkg); 558 | end; 559 | 560 | function TMainForm.CreateNode(AParent: PVirtualNode; ADisplayName: string; 561 | APackage: TPackage): PVirtualNode; 562 | var AData: PNdPackageData; 563 | begin 564 | Result := vtPackages.AddChild(AParent); 565 | vtPackages.ReinitNode(Result, false); 566 | AData := vtPackages.GetNodeData(Result); 567 | AData.DisplayName := ADisplayName; 568 | AData.Package := APackage; 569 | vtPackages.IsVisible[Result] := IsPackageNodeVisible(Result); //not enough though, see UpdateNodeVisibility 570 | end; 571 | 572 | function TMainForm.FindNode(AParent: PVirtualNode; ADisplayName: string): PVirtualNode; 573 | var node: PVirtualNode; 574 | NodeData: PNdPackageData; 575 | begin 576 | Result := nil; 577 | for node in vtPackages.ChildNodes(AParent) do begin 578 | NodeData := vtPackages.GetNodeData(Node); 579 | if SameText(NodeData.DisplayName, ADisplayName) then begin 580 | Result := node; 581 | break; 582 | end; 583 | end; 584 | end; 585 | 586 | procedure TMainForm.vtPackagesGetNodeDataSize(Sender: TBaseVirtualTree; 587 | var NodeDataSize: Integer); 588 | begin 589 | NodeDataSize := SizeOf(TNdPackageData); 590 | end; 591 | 592 | procedure TMainForm.vtPackagesInitNode(Sender: TBaseVirtualTree; ParentNode, 593 | Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); 594 | var Data: PNdPackageData; 595 | begin 596 | Data := Sender.GetNodeData(Node); 597 | Initialize(Data^); 598 | end; 599 | 600 | procedure TMainForm.vtPackagesFreeNode(Sender: TBaseVirtualTree; 601 | Node: PVirtualNode); 602 | var Data: PNdPackageData; 603 | begin 604 | Data := Sender.GetNodeData(Node); 605 | Finalize(Data^); 606 | end; 607 | 608 | procedure TMainForm.vtPackagesGetText(Sender: TBaseVirtualTree; 609 | Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; 610 | var CellText: string); 611 | var Data: PNdPackageData; 612 | begin 613 | if TextType <> ttNormal then exit; 614 | Data := Sender.GetNodeData(Node); 615 | case Column of 616 | 0, NoColumn: 617 | CellText := Data.DisplayName; 618 | end; 619 | end; 620 | 621 | procedure TMainForm.vtPackagesPaintText(Sender: TBaseVirtualTree; 622 | const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; 623 | TextType: TVSTTextType); 624 | var Data: PNdPackageData; 625 | begin 626 | Data := Sender.GetNodeData(Node); 627 | if Data.Package <> nil then //is a package 628 | if Data.Package.CbsVisibility=1 then //visible 629 | TargetCanvas.Font.Color := clBlue 630 | else 631 | TargetCanvas.Font.Color := RGB(135, 153, 255) //light blue 632 | else 633 | TargetCanvas.Font.Color := clBlack; 634 | end; 635 | 636 | procedure TMainForm.cbShowWOW64Click(Sender: TObject); 637 | begin 638 | UpdateNodeVisibility(); 639 | end; 640 | 641 | procedure TMainForm.rbGroupEachPartClick(Sender: TObject); 642 | begin 643 | Reload; 644 | end; 645 | 646 | procedure TMainForm.UpdateNodeVisibility(); 647 | begin 648 | //Big idea with visibility: 649 | //Leaf nodes (packages) are visible or invisible according to a set of rules - 650 | //see IsPackageNodeVisible. 651 | //Group nodes are by default all invisible, but are made visible as required 652 | //to show all visible leaf nodes. 653 | //It is therefore hard to update visibility on just one leaf node, as this can 654 | //potentially change visibility on group nodes up to the top. 655 | vtPackages.BeginUpdate; 656 | try 657 | //Groups have to stay visible when one of their child nodes is visible, 658 | //thus this complicated 3-step way 659 | vtPackages.IterateSubtree(nil, ResetVisibility_Callback, nil); 660 | vtPackages.IterateSubtree(nil, UpdatePackageVisibility_Callback, nil); 661 | vtPackages.IterateSubtree(nil, ApplyVisibility_Callback, nil); 662 | finally 663 | vtPackages.EndUpdate; 664 | end; 665 | 666 | FVisiblePackages := 0; 667 | vtPackages.IterateSubtree(nil, CountVisiblePackages_Callback, @FVisiblePackages); 668 | UpdateFormCaption; 669 | end; 670 | 671 | procedure TMainForm.ResetVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; 672 | Data: Pointer; var Abort: Boolean); 673 | var NodeData: PNdPackageData; 674 | begin 675 | NodeData := Sender.GetNodeData(Node); 676 | NodeData.IsVisible := false; 677 | end; 678 | 679 | procedure TMainForm.UpdatePackageVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; 680 | Data: Pointer; var Abort: Boolean); 681 | var NodeData: PNdPackageData; 682 | begin 683 | NodeData := Sender.GetNodeData(Node); 684 | if (NodeData.Package <> nil) and IsPackageNodeVisible(Node) then begin //save on re-scanning the parents when nothing changed 685 | NodeData.IsVisible := True; 686 | Node := Node.Parent; 687 | while (Node <> nil) and (Node <> Sender.RootNode) do begin 688 | NodeData := Sender.GetNodeData(Node); 689 | if NodeData <> nil then 690 | NodeData.IsVisible := true; 691 | Node := Node.Parent; 692 | end; 693 | end; 694 | end; 695 | 696 | procedure TMainForm.ApplyVisibility_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; 697 | Data: Pointer; var Abort: Boolean); 698 | var NodeData: PNdPackageData; 699 | begin 700 | NodeData := Sender.GetNodeData(Node); 701 | Sender.IsVisible[Node] := NodeData.IsVisible; 702 | end; 703 | 704 | function TMainForm.IsPackageNodeVisible(ANode: PVirtualNode): boolean; 705 | var NodeData: PNdPackageData; 706 | begin 707 | NodeData := vtPackages.GetNodeData(ANode); 708 | Result := true; 709 | if (not cbShowHidden.Checked) and (NodeData.Package <> nil) and (NodeData.Package.CbsVisibility <> 1) then 710 | Result := false; 711 | if (not cbShowWOW64.Checked) and (NodeData.Package <> nil) and SameText(NodeData.Package.Variation, 'WOW64') then 712 | Result := false; 713 | if (not cbShowKB.Checked) and NodeData.DisplayName.StartsWith('Package_') then 714 | Result := false; 715 | if (edtFilter.Text <> '') and (pos(LowerCase(edtFilter.Text), LowerCase(NodeData.DisplayName))<=0) 716 | and ((NodeData.Package=nil) or (pos(LowerCase(edtFilter.Text), LowerCase(NodeData.Package.Name))<=0)) then 717 | Result := false; 718 | end; 719 | 720 | procedure TMainForm.CountVisiblePackages_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean); 721 | var NodeData: PNdPackageData; 722 | begin 723 | NodeData := Sender.GetNodeData(Node); 724 | if (NodeData.Package <> nil) and Sender.IsVisible[Node] then 725 | Inc(PInteger(Data)^); 726 | end; 727 | 728 | 729 | //Returns a list of all Packages in the tree 730 | function TMainForm.GetAllPackages: TPackageArray; 731 | begin 732 | SetLength(Result, 0); 733 | vtPackages.IterateSubtree(nil, GetPackages_Callback, @Result) 734 | end; 735 | 736 | //Returns a list of all Packages in all selected nodes and its subnodes 737 | function TMainForm.GetSelectedPackages: TPackageArray; 738 | var node: PVirtualNode; 739 | begin 740 | SetLength(Result, 0); 741 | for node in vtPackages.SelectedNodes() do 742 | vtPackages.IterateSubtree(node, GetPackages_Callback, @Result, [vsVisible]); 743 | end; 744 | 745 | //Returns a list of all Packages in this node and its children 746 | function TMainForm.GetChildPackages(ANode: PVirtualNode): TPackageArray; 747 | begin 748 | SetLength(Result, 0); 749 | vtPackages.IterateSubtree(ANode, GetPackages_Callback, @Result, [vsVisible]) 750 | end; 751 | 752 | procedure TMainForm.GetPackages_Callback(Sender: TBaseVirtualTree; Node: PVirtualNode; 753 | Data: Pointer; var Abort: Boolean); 754 | var List: PPackageArray absolute Data; 755 | NodeData: PNdPackageData; 756 | begin 757 | NodeData := Sender.GetNodeData(Node); 758 | if NodeData.Package <> nil then begin 759 | //This is required if we allow multiselect on different levels 760 | //Both parent and one of its children can be selected independently => doubles 761 | if IsPackageInList(List^, NodeData.Package) then 762 | exit; 763 | SetLength(List^, Length(List^)+1); 764 | List^[Length(List^)-1] := NodeData.Package; 765 | end; 766 | end; 767 | 768 | //Returns a list of package names for all Packages in all selected nodes and its subnodes 769 | function TMainForm.GetSelectedPackageNames: TStringArray; 770 | begin 771 | Result := PackagesToPackageNames(GetSelectedPackages()); 772 | end; 773 | 774 | //Returns a list of package names for all Packages in this node and its children 775 | function TMainForm.GetChildPackageNames(ANode: PVirtualNode): TStringArray; 776 | begin 777 | Result := PackagesToPackageNames(GetChildPackages(ANode)); 778 | end; 779 | 780 | 781 | //Saves a list of package names into a file of user choosing 782 | procedure TMainForm.SavePackageList(APackageNames: TStringArray); 783 | var sl: TStringList; 784 | item: string; 785 | begin 786 | if not PackageListSaveDialog.Execute then exit; 787 | 788 | TArray.Sort(APackageNames); 789 | 790 | sl := TStringList.Create; 791 | try 792 | for item in APackageNames do 793 | sl.Add(item); 794 | sl.SaveToFile(PackageListSaveDialog.Filename); 795 | finally 796 | FreeAndNil(sl); 797 | end; 798 | end; 799 | 800 | procedure TMainForm.pmSavePackageListClick(Sender: TObject); 801 | begin 802 | SavePackageList(PackagesToPackageNames(GetAllPackages())); 803 | end; 804 | 805 | 806 | procedure TMainForm.PopupMenuPopup(Sender: TObject); 807 | var Packages: TPackageArray; 808 | HaveVisible, HaveInvisible: boolean; 809 | HaveVisibilityChanged: boolean; 810 | i: integer; 811 | begin 812 | Packages := GetSelectedPackages(); 813 | pmCopyPackageNames.Visible := Length(Packages)>=1; 814 | pmCopyUninstallationCommands.Visible := Length(Packages)>=1; 815 | pmUninstall.Visible := Length(Packages)=1; 816 | pmUninstallAll.Visible := Length(Packages)>1; 817 | 818 | HaveVisible := false; 819 | HaveInvisible := false; 820 | HaveVisibilityChanged := false; 821 | for i := 0 to Length(Packages)-1 do begin 822 | if Packages[i].CbsVisibility=1 then 823 | HaveVisible := true 824 | else 825 | HaveInvisible := true; 826 | if Packages[i].DefaultCbsVisibility <> Packages[i].CbsVisibility then 827 | HaveVisibilityChanged := true; 828 | end; 829 | pmMakeVisible.Visible := HaveInvisible; 830 | pmMakeInvisible.Visible := HaveVisible; 831 | pmRestoreDefaultVisibility.Visible := HaveVisibilityChanged; 832 | pmVisibility.Visible := pmMakeVisible.Visible or pmMakeInvisible.Visible 833 | or pmRestoreDefaultVisibility.Visible; 834 | end; 835 | 836 | procedure TMainForm.pmReloadClick(Sender: TObject); 837 | begin 838 | Reload; 839 | end; 840 | 841 | procedure TMainForm.pmCopyPackageNamesClick(Sender: TObject); 842 | var PackageNames: TStringArray; 843 | begin 844 | PackageNames := GetSelectedPackageNames(); 845 | if Length(PackageNames) <= 0 then exit; 846 | Clipboard.SetTextBuf(PChar(SepJoin(PackageNames, #13))); 847 | end; 848 | 849 | procedure TMainForm.DismUninstall(const APackageName: string); 850 | var ANames: TStringArray; 851 | begin 852 | SetLength(ANames, 1); 853 | ANames[0] := APackageName; 854 | DismUninstall(ANames); 855 | end; 856 | 857 | procedure TMainForm.DismUninstall(const APackageNames: TStringArray); 858 | var processInfo: TProcessInformation; 859 | err: cardinal; 860 | APackageNamesStr: string; 861 | AName: string; 862 | begin 863 | APackageNamesStr := ''; 864 | for AName in APackageNames do 865 | APackageNamesStr := APackageNamesStr + ' /Packagename='+AName; 866 | processInfo := StartProcess(GetSystemDir()+'\dism.exe', 867 | PChar('dism.exe /Online /Remove-Package '+APackageNamesStr)); 868 | try 869 | WaitForSingleObject(processInfo.hProcess, INFINITE); 870 | if not GetExitCodeProcess(processInfo.hProcess, err) then 871 | RaiseLastOsError; 872 | case err of 873 | 0: begin end; // OK 874 | ERROR_SUCCESS_REBOOT_REQUIRED: begin end; 875 | CBS_E_INVALID_PACKAGE: 876 | MessageBox(Self.Handle, PChar('Uninstall says there''s no such package. Perhaps refresh? '+ 877 | 'Or maybe you have forgotten to make packages visible. This also sometimes happens when the '+ 878 | 'package is marked for deletion until reboot.'), 879 | PChar('Uninstall failed'), MB_ICONERROR); 880 | else 881 | MessageBox(Self.Handle, PChar('Uninstall seems to have failed with error code '+IntToStr(err)), 882 | PChar('Uninstall failed'), MB_ICONERROR); 883 | end; 884 | 885 | finally 886 | CloseHandle(processInfo.hProcess); 887 | CloseHandle(processInfo.hThread); 888 | end; 889 | end; 890 | 891 | procedure TMainForm.pmUninstallAllClick(Sender: TObject); 892 | var PackageNames: TStringArray; 893 | AConfirmationText: string; 894 | begin 895 | PackageNames := GetSelectedPackageNames(); 896 | if Length(PackageNames) <= 0 then exit; 897 | 898 | //Sort the array in reverse, so that packages with ~EN_us suffixes are deleted earlier (otherwise they get dependency-deleted first) 899 | //Ideally we should just check dependencies and skip stuff already in it 900 | TArray.Sort(PackageNames, TDelegatedComparer.Construct( 901 | function(const Left, Right: string): Integer 902 | begin 903 | Result := CompareText(Left, Right); 904 | end)); 905 | 906 | if Length(PackageNames) = 1 then 907 | AConfirmationText := 'Do you really want to uninstall'#13 908 | +PackageNames[0]+'?'+#13 909 | +'After uninstalling, it will be impossible to install again without repairing Windows.' 910 | else 911 | AConfirmationText := 'Do you really want to uninstall '+IntToStr(Length(PackageNames))+' packages?'#13 912 | +SepJoin(PackageNames, #13)+#13 913 | +'After uninstalling, it will be impossible to install again without repairing Windows.'; 914 | 915 | if MessageBox(Self.Handle, PChar(AConfirmationText), 916 | PChar('Confirm uninstall'), MB_ICONWARNING or MB_YESNO) <> ID_YES then 917 | exit; 918 | 919 | DismUninstall(PackageNames); 920 | Reload; 921 | end; 922 | 923 | procedure TMainForm.pmCopyUninstallationCommandsClick(Sender: TObject); 924 | var PackageNames: TStringArray; 925 | AText, APackageName: string; 926 | begin 927 | PackageNames := GetSelectedPackageNames(); 928 | if Length(PackageNames) <= 0 then exit; 929 | 930 | AText := 'dism.exe /Online /Remove-Package'; 931 | for APackageName in PackageNames do 932 | AText := AText + ' /PackageName='+APackageName; 933 | 934 | Clipboard.SetTextBuf(PChar(AText)); 935 | end; 936 | 937 | procedure TMainForm.Saveselectedpackagelist1Click(Sender: TObject); 938 | var PackageNames: TStringArray; 939 | begin 940 | PackageNames := GetSelectedPackageNames(); 941 | if Length(PackageNames) <= 0 then exit; 942 | 943 | SavePackageList(PackageNames); 944 | end; 945 | 946 | procedure TMainForm.pmTakeRegistryOwnershipClick(Sender: TObject); 947 | begin 948 | JobProcessorForm.Caption := 'Taking ownership...'; 949 | JobProcessorForm.Show(); 950 | JobProcessorForm.Process(TTakeOwnershipJob.Create()) 951 | end; 952 | 953 | procedure TMainForm.pmDecoupleAllPackagesClick(Sender: TObject); 954 | begin 955 | JobProcessorForm.Caption := 'Decoupling...'; 956 | JobProcessorForm.Show(); 957 | JobProcessorForm.Process(TDecouplePackagesJob.Create(nil)) 958 | end; 959 | 960 | procedure TMainForm.pmDecouplePackagesClick(Sender: TObject); 961 | var packages: TStringArray; 962 | begin 963 | packages := GetSelectedPackageNames(); 964 | if Length(packages) <= 0 then exit; //because that would mean "Decouple all" 965 | JobProcessorForm.Caption := 'Decoupling...'; 966 | JobProcessorForm.Show(); 967 | JobProcessorForm.Process(TDecouplePackagesJob.Create(packages)) 968 | end; 969 | 970 | procedure TMainForm.pmMakeVisibleClick(Sender: TObject); 971 | begin 972 | SetCbsVisibility(GetSelectedPackages(), 1); 973 | end; 974 | 975 | procedure TMainForm.pmMakeInvisibleClick(Sender: TObject); 976 | begin 977 | SetCbsVisibility(GetSelectedPackages(), 2); 978 | end; 979 | 980 | procedure TMainForm.pmRestoreDefaultVisibilityClick(Sender: TObject); 981 | begin 982 | SetCbsVisibility(GetSelectedPackages(), -1); 983 | end; 984 | 985 | procedure TMainForm.pmMakeAllVisibileClick(Sender: TObject); 986 | begin 987 | SetCbsVisibility(GetAllPackages(), 1); 988 | end; 989 | 990 | procedure TMainForm.pmMakeAllInvisibleClick(Sender: TObject); 991 | begin 992 | SetCbsVisibility(GetAllPackages(), 2); 993 | end; 994 | 995 | procedure TMainForm.pmRestoreDefaltVisibilityAllClick(Sender: TObject); 996 | begin 997 | SetCbsVisibility(GetAllPackages(), -1); 998 | end; 999 | 1000 | 1001 | procedure TMainForm.edtFilterChange(Sender: TObject); 1002 | begin 1003 | UpdateNodeVisibility; 1004 | end; 1005 | 1006 | procedure TMainForm.edtFilterKeyDown(Sender: TObject; var Key: Word; 1007 | Shift: TShiftState); 1008 | begin 1009 | if Key = VK_ESCAPE then 1010 | TEdit(Sender).Text := ''; 1011 | end; 1012 | 1013 | procedure TMainForm.vtPackagesFocusChanged(Sender: TBaseVirtualTree; 1014 | Node: PVirtualNode; Column: TColumnIndex); 1015 | begin 1016 | if Assigned(pcPageInfo.ActivePage.OnEnter) then 1017 | pcPageInfo.ActivePage.OnEnter(pcPageInfo.ActivePage); 1018 | end; 1019 | 1020 | procedure TMainForm.tsInfoEnter(Sender: TObject); 1021 | var xml: IXmlDocument; 1022 | NodeData: PNdPackageData; 1023 | assembly, package, node: IXmlNode; 1024 | assemblyName: string; 1025 | copyright: string; 1026 | i: integer; 1027 | begin 1028 | lblDescription.Caption := ''; 1029 | lbUpdates.Items.Clear; 1030 | 1031 | NodeData := vtPackages.GetNodeData(vtPackages.FocusedNode); 1032 | if (NodeData = nil) or (NodeData.Package = nil) then 1033 | exit; 1034 | 1035 | xml := TXmlDocument.Create(GetWindowsDir()+'\servicing\Packages\'+NodeData.Package.Name+'.mum'); 1036 | try 1037 | assembly := xml.ChildNodes['assembly']; 1038 | if assembly = nil then exit; 1039 | package := assembly.ChildNodes['package']; 1040 | 1041 | assemblyName := ''; 1042 | if assembly.HasAttribute('name') then 1043 | assemblyName := assemblyName + assembly.Attributes['name']+#13; 1044 | if assembly.HasAttribute('description') then 1045 | assemblyName := assemblyName + assembly.Attributes['description']+#13; 1046 | if (assemblyName='') and (package <> nil) then begin 1047 | if package.HasAttribute('name') then 1048 | assemblyName := assemblyName + package.Attributes['name']+#13; 1049 | if package.HasAttribute('description') then 1050 | assemblyName := assemblyName + package.Attributes['description']+#13; 1051 | end; 1052 | //If nothing else, use registry package name 1053 | if assemblyName='' then 1054 | if (package <> nil) and (package.HasAttribute('identifier')) then 1055 | lblDescription.Caption := package.Attributes['identifier'] 1056 | else 1057 | lblDescription.Caption := NodeData.Package.Name; 1058 | 1059 | copyright := ''; 1060 | if assembly.HasAttribute('copyright') then 1061 | copyright := copyright + assembly.Attributes['copyright']; 1062 | if (copyright = '') and (package <> nil) then begin 1063 | if package.HasAttribute('copyright') then 1064 | copyright := copyright + package.Attributes['copyright']; 1065 | end; 1066 | 1067 | lblDescription.Caption := assemblyName; 1068 | if copyright <> '' then 1069 | lblDescription.Caption := lblDescription.Caption + #13 + copyright; 1070 | 1071 | if package <> nil then 1072 | for i := 0 to package.ChildNodes.Count-1 do begin 1073 | node := package.ChildNodes[i]; 1074 | if node.NodeName='update' then begin 1075 | assemblyName := ''; 1076 | if node.HasAttribute('displayName') then 1077 | assemblyName := assemblyName + node.Attributes['displayName'] + ' '; 1078 | if node.HasAttribute('description') then 1079 | assemblyName := assemblyName + node.Attributes['description'] + ' '; 1080 | if assemblyName = '' then 1081 | assemblyName := node.Attributes['name']; 1082 | lbUpdates.Items.Add(assemblyName); 1083 | end; 1084 | end; 1085 | 1086 | finally 1087 | xml := nil; 1088 | end; 1089 | end; 1090 | 1091 | 1092 | function textAttribute(const ANode: IXmlNode; const AAttribName: string): string; 1093 | begin 1094 | if ANode.HasAttribute(AAttribName) then 1095 | Result := ANode.attributes[AAttribName] 1096 | else 1097 | Result := ''; 1098 | end; 1099 | 1100 | //Parses a given assemblyIdentity node, extracting all the fields that identify an assembly 1101 | function XmlReadAssemblyIdentityData(const ANode: IXmlNode): TAssemblyIdentity; 1102 | begin 1103 | Result.name := textAttribute(ANode, 'name'); 1104 | Result.type_ := textAttribute(ANode, 'type'); 1105 | Result.language := textAttribute(ANode, 'language'); 1106 | Result.buildType := textAttribute(ANode, 'buildType'); 1107 | Result.processorArchitecture := textAttribute(ANode, 'processorArchitecture'); 1108 | Result.version := textAttribute(ANode, 'version'); 1109 | Result.publicKeyToken := textAttribute(ANode, 'publicKeyToken'); 1110 | Result.versionScope := textAttribute(ANode, 'versionScope'); 1111 | end; 1112 | 1113 | 1114 | type 1115 | TXmlNodeList = array of IXmlNode; 1116 | 1117 | function ListPackageAssemblies(xml: IXmlDocument): TXmlNodeList; overload; 1118 | var assembly, package, update, component, node: IXmlNode; 1119 | i, j: integer; 1120 | begin 1121 | SetLength(Result, 0); 1122 | 1123 | assembly := xml.ChildNodes['assembly']; 1124 | if assembly = nil then exit; 1125 | package := assembly.ChildNodes['package']; 1126 | if package = nil then exit; 1127 | 1128 | for i := 0 to package.ChildNodes.Count-1 do begin 1129 | update := package.ChildNodes[i]; 1130 | if update.NodeName <> 'update' then continue; 1131 | 1132 | for j := 0 to update.ChildNodes.Count-1 do begin 1133 | component := update.ChildNodes[j]; 1134 | if component.NodeName <> 'component' then continue; 1135 | 1136 | node := component.ChildNodes.FindNode('assemblyIdentity'); 1137 | if node = nil then continue; //that component wasn't assembly. huh. 1138 | 1139 | SetLength(Result, Length(Result)+1); 1140 | Result[Length(Result)-1] := node; 1141 | end; 1142 | end; 1143 | end; 1144 | 1145 | 1146 | procedure Log(const msg: string); 1147 | begin 1148 | MessageBox(0, PChar(msg), PChar('Log'), 0); 1149 | end; 1150 | 1151 | procedure TMainForm.tsResourcesEnter(Sender: TObject); 1152 | var xml: IXmlDocument; 1153 | NodeData: PNdPackageData; 1154 | node: IXmlNode; 1155 | assemblyData: TAssemblyIdentity; 1156 | assemblyId: TAssemblyId; 1157 | begin 1158 | FResources.Assemblies.Clear; 1159 | NodeData := vtPackages.GetNodeData(vtPackages.FocusedNode); 1160 | if (NodeData = nil) or (NodeData.Package = nil) then 1161 | exit; 1162 | 1163 | xml := TXmlDocument.Create(GetWindowsDir()+'\servicing\Packages\'+NodeData.Package.Name+'.mum'); 1164 | try 1165 | for node in ListPackageAssemblies(xml) do begin 1166 | assemblyData := XmlReadAssemblyIdentityData(node); 1167 | assemblyId := FDb.Assemblies.NeedAssembly(assemblyData); 1168 | FResources.Assemblies.Add(assemblyId); 1169 | end; 1170 | finally 1171 | xml := nil; 1172 | end; 1173 | FResources.Reload; 1174 | end; 1175 | 1176 | 1177 | procedure TMainForm.Exit1Click(Sender: TObject); 1178 | begin 1179 | Close; 1180 | end; 1181 | 1182 | procedure TMainForm.pmOpenCBSRegistryClick(Sender: TObject); 1183 | begin 1184 | RegeditOpenAndNavigate('HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\' 1185 | +'CurrentVersion\Component Based Servicing'); 1186 | end; 1187 | 1188 | procedure TMainForm.Diskcleanup1Click(Sender: TObject); 1189 | begin 1190 | StartProcess(GetSystemDir()+'\cleanmgr.exe', 'cleanmgr.exe'); 1191 | end; 1192 | 1193 | procedure TMainForm.Optionalfeatures1Click(Sender: TObject); 1194 | begin 1195 | StartProcess(GetSystemDir()+'\OptionalFeatures.exe', 'OptionalFeatures.exe'); 1196 | end; 1197 | 1198 | procedure TMainForm.DismCleanup1Click(Sender: TObject); 1199 | begin 1200 | StartProcess(GetSystemDir()+'\dism.exe', 1201 | PChar('dism.exe /Online /Cleanup-Image /StartComponentCleanup')); 1202 | end; 1203 | 1204 | procedure TMainForm.pmUninstallByListClick(Sender: TObject); 1205 | var lines: TStringList; 1206 | line: string; 1207 | i, i_pos: integer; 1208 | packages: TPackageArray; 1209 | packageNames: TStringArray; 1210 | begin 1211 | if not UninstallListOpenDialog.Execute then 1212 | exit; 1213 | 1214 | SetLength(packages, 0); 1215 | 1216 | lines := TStringList.Create; 1217 | try 1218 | lines.LoadFromFile(UninstallListOpenDialog.FileName); 1219 | 1220 | for i := 0 to lines.Count-1 do begin 1221 | line := Trim(lines[i]); 1222 | if line = '' then continue; 1223 | if line.StartsWith('//') then continue; 1224 | 1225 | // #-style comments are also supported at the end of the line 1226 | i_pos := pos('#', line); 1227 | if i_pos > 0 then begin 1228 | line := Trim(copy(line, 1, i_pos-1)); 1229 | if line = '' then continue; 1230 | end; 1231 | 1232 | FPackages.SelectMatching(line, packages); 1233 | end; 1234 | finally 1235 | FreeAndNil(lines); 1236 | end; 1237 | 1238 | packageNames := PackagesToPackageNames(packages); 1239 | 1240 | if Length(packageNames) <= 0 then begin 1241 | MessageBox(Self.Handle, PChar('Nothing to remove.'), PChar('Uninstall by list'), MB_ICONINFORMATION + MB_OK); 1242 | exit; 1243 | end else 1244 | if MessageBox(Self.Handle, PChar(IntToStr(Length(packageNames))+' packages is going to be removed. Do you really want to do this?'), 1245 | PChar('Confirm removal'), MB_ICONQUESTION + MB_YESNO) <> ID_YES then 1246 | exit; 1247 | 1248 | DismUninstall(packageNames); 1249 | Reload; 1250 | end; 1251 | 1252 | procedure TMainForm.Rebuildassemblydatabase1Click(Sender: TObject); 1253 | begin 1254 | RebuildAssemblyDatabase(FDb, AppFolder+'\assembly.db'); 1255 | end; 1256 | 1257 | end. 1258 | --------------------------------------------------------------------------------