0x8000FFFF 22 Posted March 30, 2021 (edited) There are at least 4 APIs ta parse names as PIDL or IShellItem in Shell Namespace: SHParseDisplayName SHCreateItemFromParsingName IShellFolder.ParseDisplayName ILCreateFromPath (SHILCreateFromPath) They all work fine for regular file system items, but fail to parse names to files and folders an portable devices. I obtain these names by letting the user to select a file in TVirtualExplorerEasyListview control and calling SHGetNameFromIDList with selected (absolute) PIDL and SIGDN_DESKTOPABSOLUTEPARSING. I store the name as string because the application comes back to it later (possibly after the application was closed and opened again) to process the file. At that point I need to parse PIDL from Name and bind it to IStream object to read the content of the file. It makes me sad that I can't restore PIDL from parsing names of some items. The typical parsing name is ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672}\{703540AE-0000-0000-0000-000000000000}\{0AE02E9E-0000-0000-0000-000000000000} which consists of these parts: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} This PC \\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} Redmi 6 (device name) SID-{20001,,31611420672} SD card {703540AE-0000-0000-0000-000000000000} DCIM {0AE02E9E-0000-0000-0000-000000000000} Camera I came across this thread which mentions that there's something broken in Windows 10 ver. 1703 (Creators Update). It also pointed my attention to the part that starts with SID-. When I replace this part with its display name (SD card), the parsing APIs start to work. I created this sample (commandline) application to test the parsing APIs: program Parse; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Win.ComObj, Winapi.Windows, Winapi.ActiveX, Winapi.ShlObj; type TParseFunc = function(const Name: string): PItemIDList; function GetBindCtx: IBindCtx; begin Result := nil; end; function ParseUsingSHParseDisplayName(const Name: string): PItemIDList; begin OLECheck(SHParseDisplayName(PChar(Name), GetBindCtx, Result, 0, PULONG(nil)^)); end; function ParseUsingSHCreateItemFromParsingName(const Name: string): PItemIDList; var ShellItem: IShellItem; begin OleCheck(SHCreateItemFromParsingName(PChar(Name), GetBindCtx, IShellItem, ShellItem)); OleCheck(SHGetIDListFromObject(ShellItem, Result)); end; function ParseUsingDesktopParseDisplayName(const Name: string): PItemIDList; var Desktop: IShellFolder; begin OleCheck(SHGetDesktopFolder(Desktop)); OleCheck(Desktop.ParseDisplayName(0, GetBindCtx, PChar(Name), PULONG(nil)^, Result, PULONG(nil)^)); end; function ParseUsingILCreateFromPath(const Name: string): PItemIDList; begin Result := ILCreateFromPath(PChar(Name)); if not Assigned(Result) then RaiseLastOSError; end; procedure TestParse(const MethodName, Name: string; Parse: TParseFunc); var PIDL: PItemIDList; PName: PWideChar; begin Writeln(MethodName); try PIDL := Parse(Name); try if Succeeded(SHGetNameFromIDList(PIDL, Integer(SIGDN_DESKTOPABSOLUTEEDITING), PName)) then begin Writeln('Display name: ', PName); CoTaskMemFree(PName); end; if Succeeded(SHGetNameFromIDList(PIDL, Integer(SIGDN_DESKTOPABSOLUTEPARSING), PName)) then begin Writeln('Parsing name: ', PName); CoTaskMemFree(PName); end; finally CoTaskMemFree(PIDL); end; except on E: Exception do Writeln('[', E.ClassName, '] ', E.Message); end; Writeln; end; procedure Main; var Name: string; begin Name := ParamStr(1); Writeln('Name: ', Name); TestParse('SHParseDisplayName', Name, ParseUsingSHParseDisplayName); TestParse('SHCreateItemFromParsingName', Name, ParseUsingSHCreateItemFromParsingName); TestParse('Desktop.ParseDisplayName', Name, ParseUsingDesktopParseDisplayName); TestParse('ILCreateFromPath', Name, ParseUsingILCreateFromPath); end; begin CoInitialize(nil); Main; CoUninitialize; end. This is what I got with various names on input: Parse.exe Quote Name: SHParseDisplayName Display name: This PC Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} SHCreateItemFromParsingName Display name: This PC Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} Desktop.ParseDisplayName Display name: This PC Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} ILCreateFromPath Display name: This PC Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} Parse.exe C:\ Quote Name: C:\ SHParseDisplayName Display name: C:\ Parsing name: C:\ SHCreateItemFromParsingName Display name: C:\ Parsing name: C:\ Desktop.ParseDisplayName Display name: C:\ Parsing name: C:\ ILCreateFromPath Display name: C:\ Parsing name: C:\ Parse.exe "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}" Quote Name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} SHParseDisplayName Display name: This PC\Redmi 6 Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} SHCreateItemFromParsingName Display name: This PC\Redmi 6 Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} Desktop.ParseDisplayName Display name: This PC\Redmi 6 Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} ILCreateFromPath Display name: This PC\Redmi 6 Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33} Parse.exe "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672}" Quote Name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672} SHParseDisplayName [EOleSysError] The parameter is incorrect SHCreateItemFromParsingName [EOleSysError] The parameter is incorrect Desktop.ParseDisplayName [EOleSysError] The parameter is incorrect ILCreateFromPath [Exception] ILCreateFromPath failed. Parse.exe "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SD card" Quote Name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SD card SHParseDisplayName Display name: This PC\Redmi 6\SD card Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672} SHCreateItemFromParsingName Display name: This PC\Redmi 6\SD card Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672} Desktop.ParseDisplayName Display name: This PC\Redmi 6\SD card Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672} ILCreateFromPath Display name: This PC\Redmi 6\SD card Parsing name: ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672} At this point if I use folder name (DCIM\Camera) or GUID ({703540AE-0000-0000-0000-000000000000}\{0AE02E9E-0000-0000-0000-000000000000}) from original parsing name beyond SD card, all of that will work. There is also Internal storage at the same level as SD card, which has name for parsing SID-{10001,,25710370816}. Parsing APIs fail to parse this name either. I have also tried other devices with the same results. I don't have pre-1703 Windows 10 or older Windows system at hand to try that, but I want my application to work on any Windows 7+ platform. Can anybody explain what is going on here or point me to some relevant resources? Edited March 30, 2021 by 0x8000FFFF Share this post Link to post
KodeZwerg 54 Posted March 31, 2021 strange activity you are performing. for myself havent tried it direct like you. if you like, you can try my FileShell tool on your device, it is basical a PIDL walker. Share this post Link to post
0x8000FFFF 22 Posted April 7, 2021 (edited) I wasn't able to identify the culprit, so I had to implement my own method for parsing PIDLs from names. There were two challenges involved: Split the name (path) of shell item into parts. Resolve relative PIDL of each part. Splitting the name by path delimiter isn't as easy as it may seem - especially when one needs to deal with ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}. I created simple enumerator for that. To get relative PIDL of each part the parsing method uses IShellFolder.ParseDisplayName. If that fails (e.g. SID-{20001,,31611420672}) it uses IShellFolder::EnumObjects to find the part either by display name or parsing name. Here's the complete code: unit ShellUtils; interface uses System.SysUtils, System.Win.ComObj, Winapi.Windows, Winapi.ActiveX, Winapi.ShlObj, Winapi.ShLwApi; type TNamePartsEnumerator = record private FName: PChar; FStart: PChar; FEnd: PChar; function GetCurrent: string; function GetEnumerated: string; function GetTail: string; public constructor Create(Name: PChar); function GetEnumerator: TNamePartsEnumerator; function MoveNext: Boolean; procedure Reset; property Current: string read GetCurrent; property Enumerated: string read GetEnumerated; property Tail: string read GetTail; end; TParseResult = record private FError: string; FOleError: HRESULT; FPIDL: PItemIDList; public procedure RaiseException; property Error: string read FError; property OleError: HRESULT read FOleError; property PIDL: PItemIDList read FPIDL; end; function ParseDisplayName(const Name: string): PItemIDList; function TryParseDisplayName(const Name: string; out ParseResult: TParseResult): Boolean; overload; function TryParseDisplayName(const Name: string; out PIDL: PItemIDList): Boolean; overload; implementation { TNamePartsEnumerator } {$POINTERMATH ON} constructor TNamePartsEnumerator.Create(Name: PChar); begin FName := Name; Reset; end; function TNamePartsEnumerator.GetCurrent: string; begin if Assigned(FStart) then SetString(Result, FStart, FEnd - FStart) else Result := string.Empty; end; function TNamePartsEnumerator.GetEnumerated: string; begin SetString(Result, FName, FEnd - FName); end; function TNamePartsEnumerator.GetEnumerator: TNamePartsEnumerator; begin Result := Self; end; function TNamePartsEnumerator.GetTail: string; begin Result := FEnd; end; function TNamePartsEnumerator.MoveNext: Boolean; var NewStart: PChar; begin if (not Assigned(FEnd)) or (FEnd^ = #0) then Exit(False); if Assigned(FStart) then NewStart := FEnd + 1 else NewStart := FEnd; // special cases: \\?\ and \\.\ if (NewStart^ = '\') and ((NewStart + 1)^ = '\') and (((NewStart + 2)^ = '?') or ((NewStart + 2)^ = '.')) and ((NewStart + 1)^ = '\') then FEnd := NewStart + 4 else begin // skip consecutive path delimiters while NewStart^ = '\' do Inc(NewStart); FEnd := NewStart; if FEnd^ = #0 then Exit(False); end; FStart := NewStart; while (FEnd^ <> #0) and (FEnd^ <> '\') do FEnd := CharNext(FEnd); Result := True; end; procedure TNamePartsEnumerator.Reset; begin FStart := nil; FEnd := FName; end; {$POINTERMATH OFF} { TParseResult } procedure TParseResult.RaiseException; begin if not FError.IsEmpty then raise Exception.Create(FError); OleCheck(FOleError); end; function GetDisplayName(const Folder: IShellFolder; PIDL: PItemIDList; SHGDNFlags: DWORD): string; var StrRet: TStrRet; DisplayName: PChar; begin if Succeeded(Folder.GetDisplayNameOf(PIDL, SHGDNFlags, StrRet)) and Succeeded(StrRetToStr(@StrRet, PIDL, DisplayName)) then begin Result := DisplayName; CoTaskMemFree(DisplayName); end else Result := ''; end; function ParseDisplayName(const Name: string): PItemIDList; var ParseResult: TParseResult; begin if not TryParseDisplayName(Name, ParseResult) then ParseResult.RaiseException; Result := ParseResult.PIDL; end; function TryParseDisplayName(const Name: string; out ParseResult: TParseResult): Boolean; const SHCONTF_INCLUDESUPERHIDDEN = $10000; var Folder, Subfolder: IShellFolder; Enumerator: TNamePartsEnumerator; RelativePIDL, CombinedPIDL: PItemIDList; EnumIDList: IEnumIDList; Part: string; begin Result := False; ParseResult := Default(TParseResult); // take short path, if possible if Succeeded(SHParseDisplayName(PChar(Name), nil, ParseResult.FPIDL, 0, PDWORD(nil)^)) then begin Result := True; Exit; end; ParseResult.FOleError := SHGetDesktopFolder(Folder); if not Succeeded(ParseResult.FOleError) then Exit; ParseResult.FOleError := SHGetIDListFromObject(Folder, ParseResult.FPIDL); if not Succeeded(ParseResult.FOleError) then Exit; try Enumerator := TNamePartsEnumerator.Create(PChar(Name)); while Enumerator.MoveNext do begin Part := Enumerator.Current; if not Succeeded(Folder.ParseDisplayName(0, nil, PChar(Part), PULONG(nil)^, RelativePIDL, PULONG(nil)^)) then begin RelativePIDL := nil; ParseResult.FOleError := Folder.EnumObjects(0, SHCONTF_FOLDERS or SHCONTF_INCLUDEHIDDEN or SHCONTF_NONFOLDERS or SHCONTF_STORAGE or SHCONTF_INCLUDESUPERHIDDEN, EnumIDList); if not Succeeded(ParseResult.FOleError) then Exit; while EnumIDList.Next(1, RelativePIDL, PULONG(nil)^) = S_OK do begin if SameFileName(Part, GetDisplayName(Folder, RelativePIDL, SHGDN_INFOLDER or SHGDN_FORPARSING)) or SameFileName(Part, GetDisplayName(Folder, RelativePIDL, SHGDN_INFOLDER or SHGDN_FORADDRESSBAR)) then Break; CoTaskMemFree(RelativePIDL); RelativePIDL := nil; end; end; if not Assigned(RelativePIDL) then begin ParseResult.FError := 'Can''t find ' + Enumerator.Enumerated; Exit; end; try CombinedPIDL := ILCombine(ParseResult.FPIDL, RelativePIDL); CoTaskMemFree(ParseResult.FPIDL); ParseResult.FPIDL := CombinedPIDL; if not Enumerator.Tail.IsEmpty then begin ParseResult.FOleError := Folder.BindToObject(RelativePIDL, nil, IShellFolder, Subfolder); if not Succeeded(ParseResult.FOleError) then Exit; Folder := Subfolder; end; finally CoTaskMemFree(RelativePIDL); end; end; Result := True; finally // release intermediate PIDL in case of failure if (not Result) and Assigned(ParseResult.FPIDL) then begin CoTaskMemFree(ParseResult.FPIDL); ParseResult.FPIDL := nil; end; end; end; function TryParseDisplayName(const Name: string; out PIDL: PItemIDList): Boolean; var ParseResult: TParseResult; begin Result := TryParseDisplayName(Name, ParseResult); PIDL := ParseResult.PIDL; end; end. For the names like ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\\?\usb#vid_2717&pid_ff40#062539717d28#{6ac27878-a6fa-4155-ba85-f98f491d4f33}\SID-{20001,,31611420672}\{703540AE-0000-0000-0000-000000000000}\{0AE02E9E-0000-0000-0000-000000000000} it falls back to EnumObjects only for SID-{20001,,31611420672} part. This method can also parse display names like This PC\Redmi 6\SD card\DCIM\Camera, because it looks up items by display name. That is also what Raymond Chen suggested in his comment under How to use SHCreateItemFromParsingName with names from the shell namespace keeping in mind that display names are ambiguous. Another thing to point out is that looking up the item by display name via EnumObjects is inefficient and it can eventually be slow. Edited April 7, 2021 by 0x8000FFFF Share this post Link to post