So on Windows XP and earlier, you had documented functions SfcGetFiles and SfcGetNextProtectedFile to get a list of all system-protected files under Windows Resource Protection. But these were either entirely removed or set to return an error in Vista+. Turns out this was just arbitrary and capricious behavior on Microsoft's part; they didn't remove the ability to generate such a list, merely locked it behind undocumented APIs GetNextFileMapContent/BeginFileMapEnumeration/CloseFileMapEnumeration. I found an existing implementation of how to use those APIs in Alex Dragokas' excellent HiJackThis program, but making that snippet simplified to use normal declares and make it x64 compatible turned out to be more challenging than expected-- I scoured the internet, checking GitHub code search, Google, Bing, and some LLMs; *nowhere* besides that module, which he credits to SSTREGG, gives any information about the arguments for those APIs, making it complete guesswork what needed to become LongPtr. (And another issue; the code as written in the module had string encoding issues).
It was still crashing after trying the most obvious combinations, until I finally correctly deduced that the size arguments in GetNextFileMapContent could possibly be size_t, which is 8 bytes under x64 instead of 4, and I finally found the right combo for the reserved arguments after making that change. Since this was tons of guesswork and non-obvious to anyone who isn't familiar with the myriad Windows data types, thought I'd post my version too:
On a Form with CommandButton Command1, with no other dependencies:
Thanks again to Dragokas and SSTREGG.
PS- The C prototypes would then be (as typedefs since you'd have to call them by pointer from GetProcAddress):
I'm guessing on the BOOL return type for the first two; only fairly sure that GetNextFileMapContent is because of the GetLastError usage (Err.LastDllError), the others could plausibly be HRESULT but since they're both 4-byte types it at least won't crash. The others are guesses with e.g. DWORD vs UINT, PVOID vs void*, etc, but are interchangeable.
It was still crashing after trying the most obvious combinations, until I finally correctly deduced that the size arguments in GetNextFileMapContent could possibly be size_t, which is 8 bytes under x64 instead of 4, and I finally found the right combo for the reserved arguments after making that change. Since this was tons of guesswork and non-obvious to anyone who isn't familiar with the myriad Windows data types, thought I'd post my version too:
On a Form with CommandButton Command1, with no other dependencies:
Code:
Option Explicit
Private Type PPROTECTED_FILE_INFO
Length As Long
FileName(259) As Integer
End Type
#If VBA7 Then
Private Declare PtrSafe Function BeginFileMapEnumeration Lib "sfc_os.dll" (ByVal Reserved0 As Long, ByVal Reserved1 As LongPtr, Handle As LongPtr) As Long
Private Declare PtrSafe Function CloseFileMapEnumeration Lib "sfc_os.dll" (ByVal Handle As LongPtr) As Long
Private Declare PtrSafe Function GetNextFileMapContent Lib "sfc_os.dll" (ByVal Reserved As Long, ByVal SfcHandle As LongPtr, ByVal Size As LongPtr, ProtectedInfo As PPROTECTED_FILE_INFO, dwNeeded As LongPtr) As Long
#Else
Private Enum LongPtr
[_]
End Enum
Private Declare Function BeginFileMapEnumeration Lib "sfc_os.dll" (ByVal Reserved0 As Long, ByVal Reserved1 As LongPtr, Handle As LongPtr) As Long
Private Declare Function CloseFileMapEnumeration Lib "sfc_os.dll" (ByVal Handle As LongPtr) As Long
Private Declare Function GetNextFileMapContent Lib "sfc_os.dll" (ByVal Reserved As Long, ByVal SfcHandle As LongPtr, ByVal Size As LongPtr, ProtectedInfo As PPROTECTED_FILE_INFO, dwNeeded As LongPtr) As Long
#End If
Private Const ERROR_NO_MORE_FILES As Long = 18
Private Const ERROR_INSUFFICIENT_BUFFER As Long = 122
Private Sub Command1_Click() 'Handles Command1.Click
Dim sf() As String
sf = SFCList_Vista()
Dim i As Long
For i = 0 To UBound(sf)
Debug.Print sf(i)
Next
End Sub
Public Function SFCList_Vista() As String()
On Error GoTo ErrorHandler:
Dim dwNeeded As LongPtr
Dim dwBufferSize As Long
Dim pData As PPROTECTED_FILE_INFO
Dim hSFC As LongPtr
Dim ret As Long
Dim SFCList() As String
Dim i As Long
ret = BeginFileMapEnumeration(0&, 0&, hSFC)
If hSFC = 0 Then
Debug.Print "Error! Cannot get handle of first element of BeginFileMapEnumeration."
Exit Function
Else
Debug.Print "Init ok"
End If
dwBufferSize = LenB(pData)
ReDim SFCList(300)
Do
ret = GetNextFileMapContent(0&, hSFC, dwBufferSize, pData, dwNeeded)
Select Case Err.LastDllError ' <--- Does not working here !!!
Case 0
If UBound(SFCList) < i Then ReDim Preserve SFCList(i + 100)
SFCList(i) = WCHARtoSTR(pData.FileName)
i = i + 1
Case ERROR_NO_MORE_FILES Or (pData.Length = 0)
Exit Do
Case ERROR_INSUFFICIENT_BUFFER Or (dwNeeded > dwBufferSize)
Debug.Print "ERROR_INSUFFICIENT_BUFFER"
End Select
If pData.Length = 0 Then Exit Do
Loop
CloseFileMapEnumeration hSFC
If i = 0 Then
ReDim SFCList(0)
Else
ReDim Preserve SFCList(i - 1)
End If
SFCList_Vista = SFCList
Exit Function
ErrorHandler:
Debug.Print "SFCList_Vista errorhandler::" & Err.Number & "->" & Err.Description
End Function
Private Function WCHARtoSTR(aCh() As Integer) As String
Dim i As Long
Dim sz As String
For i = LBound(aCh) To UBound(aCh)
If aCh(i) <> 0 Then
sz = sz & ChrW$(CLng(aCh(i)))
End If
Next
WCHARtoSTR = sz
End Function
PS- The C prototypes would then be (as typedefs since you'd have to call them by pointer from GetProcAddress):
Code:
typedef struct PPROTECTED_FILE_INFO {
DWORD length;
WCHAR FileName[MAX_PATH];
} PPROTECTED_FILE_INFO, *PPPROTECTED_FILE_INFO;
typedef BOOL (WINAPI * BeginFileMapEnumeration)(_In_ DWORD Reserved0, _Reserved_ PVOID Reserved1, _Out_ PHANDLE Handle);
typedef BOOL (WINAPI * CloseFileMapEnumeration)(_In_ HANDLE SfcHandle);
typedef BOOL (WINAPI * GetNextFileMapContent)(_In_ DWORD Reserved, _In_ HANDLE SfcHandle, _In_ SIZE_T Size, _Out_ PPROTECTED_FILE_INFO *ProtectedInfo As , _Out_ SIZE_T *dwNeeded);