2

I'm not that great with visual basic so sorry about that, but I have a basic understanding of java, and a few other programming languages so I'm not completely new.
I want to make a program that connects to a website with a proxy loaded from a text file, but when I debug it, it gives me a bunch of different error messages, like:

  • HTTP Error 503. The service is unavailable
  • The webpage cannot be found

etc.

Here is my code for loading in the proxies into a listbox:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    OpenFileDialog1.ShowDialog()
    streamer = IO.File.OpenText(OpenFileDialog1.FileName)
    Dim mystring() As String = streamer.ReadToEnd.Split(vbNewLine)

    ListBox1.Items.AddRange(mystring)
 End Sub

Here's the code for using the proxies:

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    Button2.Enabled = False
    Button1.Enabled = False

    For Each item In ListBox1.Items
        UseProxy(item.ToString)
        Label4.Text = item
        WebBrowser1.Navigate(TextBox3.Text)
        pause(10000)
    Next
End Sub

And here's useproxy:

Private Sub UseProxy(ByVal strProxy As String)
    Const INTERNET_OPTION_PROXY As Integer = 38
    Const INTERNET_OPEN_TYPE_PROXY As Integer = 3

    Dim struct_IPI As Struct_INTERNET_PROXY_INFO
    struct_IPI.dwAccessType = INTERNET_OPEN_TYPE_PROXY
    struct_IPI.proxy = Marshal.StringToHGlobalAnsi(strProxy)
    struct_IPI.proxyBypass = Marshal.StringToHGlobalAnsi("local")

    Dim intptrStruct As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(struct_IPI))

    Marshal.StructureToPtr(struct_IPI, intptrStruct, True)
    Dim iReturn As Boolean = InternetSetOption(IntPtr.Zero, INTERNET_OPTION_PROXY, intptrStruct, System.Runtime.InteropServices.Marshal.SizeOf(struct_IPI))
End Sub

I also get an error at the end of the button 2 click sub saying this

Actually sometimes when I close the program, I get an error in visual studio on the end of the button2 click sub (Next)

System.InvalidOperationException: 'List that this enumerator is bound to has been modified. An enumerator can only be used if the list does not change.'

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • You need to use `InternetSetOption` to propagate a (System) message that the registry values related to the Proxy settings have changed. Use the [Registry Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.registry?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DEN-US%26k%3Dk(Microsoft.Win32.Registry);k(TargetFrameworkMoniker-.NETFramework&view=netframework-4.7.2)) to modify these registry settings. (`ProxyServer` [string] = "Proxy:Port") and `ProxyEnable` [dword] <= Boolean is OK = 0, 1). – Jimi Jul 23 '18 at 02:38
  • Could you elaborate please? Sorry I didn't write the useproxy code. –  Jul 23 '18 at 02:43
  • You need to set those Registry values, then `InternetSetOption` must generate a "Broadcast" to let know that these settings have changed and a "Suggestion" that the interested applications should refresh their knowledge on the matter. e.g. send a `INTERNET_OPTION_SETTINGS_CHANGED = 39` message and the `INTERNET_OPTION_REFRESH = 37` message right after, both to `hInternet` = `IntPtr.Zero`. If you need an example, let me know. – Jimi Jul 23 '18 at 02:50
  • yeah I'd like an example, sorry –  Jul 23 '18 at 02:53

1 Answers1

2

I think this is the most straightforward method to change the System Proxy settings on a System-wide basis.
Sometimes, a per-connection setting is preferable. See the linked documents in this case.

Use the Registry class to set the required value in the Internet Settings Subkey:
Software\Microsoft\Windows\CurrentVersion\Internet Settings

Then, perform a System notification of the change, using InternetSetOption with the INTERNET_OPTION_SETTINGS_CHANGED and INTERNET_OPTION_REFRESH Flags:
(These are flags, but should not be combined in this case)
INTERNET_OPTION_REFRESH must also be called if INTERNET_OPTION_PER_CONNECTION_OPTION is used to modify the settings on a per-connection basis.

Declaration of InternetSetOption and a helper function.

Imports Microsoft.Win32
Imports System.Runtime.InteropServices
Imports System.Security.AccessControl

<SecurityCritical>
<DllImport("wininet.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Friend Shared Function InternetSetOption(hInternet As IntPtr, dwOption As Integer, lpBuffer As IntPtr, dwBufferLength As Integer) As Boolean
End Function
Friend Const INTERNET_OPTION_SETTINGS_CHANGED As Integer = 39
Friend Const INTERNET_OPTION_REFRESH As Integer = 37

<SecuritySafeCritical>
Private Sub InternetSetProxy(ProxyAddress As String, ProxyPort As Integer, UseProxy As Boolean)
    Dim keyName As String = "Software\Microsoft\Windows\CurrentVersion\Internet Settings"
    Dim KeyValue As Object = ProxyAddress + ":" + ProxyPort.ToString()

    Dim InternetSettingsKey As RegistryKey =
                Registry.CurrentUser.OpenSubKey(keyName,
                RegistryKeyPermissionCheck.ReadWriteSubTree,
                RegistryRights.WriteKey)

    InternetSettingsKey.SetValue("ProxyServer", KeyValue, RegistryValueKind.String)
    InternetSettingsKey.SetValue("ProxyEnable", If(UseProxy, 1, 0), RegistryValueKind.DWord)
    InternetSettingsKey.Close()

    InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0)
    InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0)

    InternetSettingsKey.Dispose()
End Sub

You can test it with Internet Explorer.
If you are using Fiddler, call the helper method with these parameters to use it as the default system proxy:

InternetSetProxy("127.0.0.1", 8888, True)

To disable the Proxy setting, just set the UseProxy parameter to False:

InternetSetProxy("127.0.0.1", 8888, False)

Navigate an Internet address as usual:

WebBrowser1.Navigate("https://www.google.com")

A note about the .Net WebBrowser control.
The WebBrowser control uses the current cache (as specified in the Internet Explorer settings) to load visited resources as the pre-defined behavior.
If you enable the Proxy but you can still see the (first) page of a Website, this depends on this default behavior.
If you try to click on a link, this new link will not be reachable, and the message:

The proxy server isn’t responding

will be presented.
This kind of result is not constant, it depends on the cache settings.

Edit:
A way to let the WebBrowser control Navigate one or more Internet addresses, using a different Proxy server for each address:

Add an event handler for the WebBrowser DocumentCompleted event.
When the WebBrowser.Navigate() method is first called, the event handler will cycle through the list of Proxies (read from a ListBox control Items list).

This code supposes that the Proxy list is composed of strings that include both the address and the port of each Proxy. (e.g. "127.0.0.1:8888" for Fiddler).

Here, as in the OP example, the Internet Address is the same for all Proxies (a parameter of the WaitDocumentsComplete() method), but of course can be read from another List.

Both the DocumentCompleted Handler and the helper method NavigateNextProxy() are marked as Asynchronous, thus the UI is not blocked throughout the whole process.

This process can be stopped at any time, using the WebBrowser.Stop() method, or setting the ProxyIndex field to a negative value.
If the process is interrupted before its natural completion, it's necessary to explicitly remove the Handler, using:

RemoveHandler WebBrowser1.DocumentCompleted, DocumentCompletedHandler

Event Handler declaration and the Wrapper and Helper functions:

Protected Friend DocumentCompletedHandler As WebBrowserDocumentCompletedEventHandler

Private Sub WaitDocumentsComplete(ByVal Address As String, ByVal TimeOutSeconds As Integer)
    DocumentCompletedHandler =
        Async Sub(s, e)
            If WebBrowser1.ReadyState = WebBrowserReadyState.Complete AndAlso ProxyIndex > -1 Then
                If (Not WebBrowser1.IsBusy) Then
                    Dim ProxyParts() As String = ListBox1.GetItemText(ListBox1.Items(ProxyIndex)).Split(":"c)
                    Await NavigateNextProxy(Address, ProxyParts(0), CInt(ProxyParts(1)), TimeOutSeconds)
                    ProxyIndex += 1
                    If ProxyIndex > ProxyIndexMaxValue Then
                        ProxyIndex = -1
                        RemoveHandler WebBrowser1.DocumentCompleted, DocumentCompletedHandler
                    End If
                End If
            End If
        End Sub
    AddHandler WebBrowser1.DocumentCompleted, DocumentCompletedHandler
End Sub

Protected Async Function NavigateNextProxy(Address As String, ByVal ProxyAddr As String, ProxyPort As Integer, TimeOutSeconds As Integer) As Task
    InternetSetProxy(ProxyAddr, ProxyPort, True)
    Await Task.Delay(TimeSpan.FromSeconds(TimeOutSeconds))
    If WebBrowser1.IsBusy Then WebBrowser1.Stop()
    WebBrowser1.Navigate(Address)
End Function

From a Button.Click() event, set the Indexes of the first and last Proxy in the List, a Timeout value (in seconds) between each navigation, and call the wrapper method WaitDocumentsComplete(), which will initialize the DocumentCompleted event handler.

To begin the process, initialize the WebBrowser control with WebBrowser1.Navigate("")

It's also important to suppress the WebBrowser script error dialog, otherwise it will compromise the whole procedure.

Private ProxyIndex As Integer = -1
Private ProxyIndexMaxValue As Integer = 0

Private Sub btnForm2_Click(sender As Object, e As EventArgs) Handles btnForm2.Click
    ProxyIndex = 0
    ProxyIndexMaxValue = ListBox1.Items.Count - 1
    Dim BrowsingTimeoutSeconds As Integer = 3
    Dim Address As String = "https://www.google.com"
    WaitDocumentsComplete(Address, BrowsingTimeoutSeconds)
    WebBrowser1.ScriptErrorsSuppressed = True
    WebBrowser1.Navigate("")
End Sub
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Nice answer! Though why the `Application.DoEvents()` call? – Visual Vincent Jul 23 '18 at 09:44
  • @Visual Vincent Well, if you are using a `WebBrowser` control, you are using a Form, which has a message queue. We, possibly, have to process that message, too. We caused a broadcast and we don't do our best to listed to it? That's rude :) – Jimi Jul 23 '18 at 09:50
  • 1
    Sure, but the message pump/queue handles itself. You shouldn't need to force it. It is a `While` loop that checks for messages all the time. – Visual Vincent Jul 23 '18 at 09:56
  • 1
    @Visual Vincent Yes, but it might become isolated. The OP, apparently, needs to perform a number of connections, changing the current Proxy address. Since it's possible that he'll end up doing this in a close circuit, I tried to *give a pause*. If you think that, in this scenario, is not useful, I have no problems to cut it out. But I think it might play a role (here). – Jimi Jul 23 '18 at 10:00
  • Interesting and perfectly valid assumption. I wouldn't know as I haven't tried it and don't know the specifics about how the implementation works (I've never tried to use proxies before). I was just curious about the call. :) – Visual Vincent Jul 23 '18 at 10:02
  • It's still doing the same thing sadly. –  Jul 23 '18 at 14:30
  • @jvadi Then the Proxy has not been modified or the strings in `ListBox1.Items` are not parsed correctly. Are you using the method (`InternetSetProxy`) as shown here? If so, you need to pass it the Proxy address and the Port in two different properties. Verify with the Debugger what values are passed to the method. Then, these Proxy addresses are verified? The corresponding service is responding to a `Ping`? Also, I don't know what `pause(10000)` is doing. Is that a Timer? A `Thread.Sleep()`? If you modify you code, update your Question with the new code, so we know what's happening. – Jimi Jul 23 '18 at 18:31
  • @jvadi Try the method you can find in the **Edit** section, used for handling (asychronously) the navigation through all the listed Proxies (it's tested). – Jimi Jul 24 '18 at 01:11