I'm p/invoking InternetSecurityManager.GetZoneMappings()
in order to get a list of safe sites from my Intranet Zone, and the Next()
method on the returned IEnumString
object always reports that zero sites were fetched, even though there were.
Here's my code, based on this answer:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Public Class Test
Private Shared ReadOnly CLSID_InternetSecurityManager As New Guid("7b8a2d94-0ac9-11d1-896c-00c04fb6bfc4")
Private ReadOnly InternetSecurityManager As IInternetSecurityManager
Private ReadOnly SecurityManager As Object
Private Const URLZONE_LOCAL_MACHINE As UInteger = &H0
Private Const URLZONE_INTRANET As UInteger = URLZONE_LOCAL_MACHINE 1
Private Const URLZONE_TRUSTED As UInteger = URLZONE_INTRANET 1
Private Const URLZONE_INTERNET As UInteger = URLZONE_TRUSTED 1
Private Const URLZONE_UNTRUSTED As UInteger = URLZONE_INTERNET 1
Public Sub New
Dim oType As Type
oType = Type.GetTypeFromCLSID(CLSID_InternetSecurityManager)
Me.SecurityManager = Activator.CreateInstance(oType)
Me.InternetSecurityManager = Me.SecurityManager
Me.GetSites()
End Sub
Private Function GetSites() As List(Of String)
Dim nItemsFetched As ULong
Dim oEnumString As IEnumString
Dim oSites As List(Of String)
Dim aItems As String()
oEnumString = Nothing
Me.InternetSecurityManager.GetZoneMappings(URLZONE_INTRANET, oEnumString, 0)
oSites = New List(Of String)
aItems = New String(0) {}
Do
oEnumString.Next(1, aItems, nItemsFetched)
If aItems.First Is Nothing Then
Exit Do
Else
oSites.Add(aItems.First)
End If
Loop
Return oSites
End Function
End Class
It runs and it does enumerate the list correctly, but I'd like to clean it up a bit by responding appropriately to the value of nItemsFetched
(rather than the present rather cumbersome check for Nothing
).
It seems that managed code samples of GetZoneMappings()
are pretty scarce out there. Plenty of signatures, but no usage. I couldn't find any (in fact, for all my searching this might be the only one). After some serious digging, though, I did manage to put my thumb on the EnumString
sample linked above.
Here's my IInternetSecurityManager
interface, gleaned from p/invoke.net:
Imports System
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
<ComImport, Guid("79EAC9EE-BAF9-11CE-8C82-00AA004BA90B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IInternetSecurityManager
<PreserveSig>
Function SetSecuritySite(
<[In]> pSite As IntPtr) As Integer
<PreserveSig>
Function GetSecuritySite(
<Out> pSite As IntPtr) As Integer
<PreserveSig>
Function MapUrlToZone(
<[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, <Out> ByRef pdwZone As UInteger, dwFlags As UInteger) As Integer
<PreserveSig>
Function GetSecurityId(
<MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String,
<MarshalAs(UnmanagedType.LPArray)> pbSecurityId As Byte(), ByRef pcbSecurityId As UInteger, dwReserved As UInteger) As Integer
<PreserveSig>
Function ProcessUrlAction(
<[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, dwAction As UInteger, <Out> ByRef pPolicy As Byte, cbPolicy As UInteger, pContext As Byte, cbContext As UInteger, dwFlags As UInteger, dwReserved As UInteger) As Integer
<PreserveSig>
Function QueryCustomPolicy(
<[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, ByRef guidKey As Guid, ByRef ppPolicy As Byte, ByRef pcbPolicy As UInteger, ByRef pContext As Byte, cbContext As UInteger, dwReserved As UInteger) As Integer
<PreserveSig>
Function SetZoneMapping(dwZone As UInteger, <[In], MarshalAs(UnmanagedType.LPWStr)> lpszPattern As String, dwFlags As UInteger) As Integer
<PreserveSig>
Function GetZoneMappings(dwZone As UInteger, <Out> ByRef ppenumString As IEnumString, dwFlags As UInteger) As Integer
End Interface
I tried building an IEnumString
interface based on this article, but I only ran into problems with that. At least the native call completes and returns data. I just want to be able to know when the data stops coming.
How can I get EnumString.Next()
to correctly return the number of items it fetches?
CodePudding user response:
Next() returns an HRESULT value, so 0 is S_OK
(success) which means it returns something in rgelt
.
So,
- you must allocate
aItems
to an array of (at least) 1 element before call; - optionally, you can pass a non null pointer to the
pceltFetched
. if you pass null, you cannot determine how many items where read; - the call should return
S_OK
(0) if something was read,S_FALSE
(1) if not all was requested was read (and possibly something else in error cases) which in thecelt
= 1 case is the same a "this was the last item in the enumeration".
So, your code can be written like this:
Do
Dim ret = oEnumString.Next(1, aItems, IntPtr.Zero)
If ret <> 0 Then Exit Do
oSites.Add(aItems.First)
Loop
Or like this if you want to use the pceltFetched
argument. This is rarely needed, only when celt
> 1 (when the return value just say that not everything was read) which is not always supported by enum sources either, so everyone always uses celt
= 1...
Dim fetchedPtr As IntPtr
fetchedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Of Integer)()) ' or AllocCoTaskMem
Dim fetched As Integer
Do
oEnumString.Next(1, aItems, fetchedPtr)
fetched = Marshal.ReadInt32(fetchedPtr)
If fetched <> 1 Then Exit Do
oSites.Add(aItems.First)
Loop
Marshal.FreeHGlobal(fetchedPtr)