VB.NET using Internet Explorer to Automate Saving Websites as HTML - html

I am trying to develop a simple tool in VB.NET that, within a loop, navigates to a website and save the page as an HTML document.
I can set up the loop easily as the pages are numbered sequentially.
www.example.com/pages/1.html
www.example.com/pages/2.html
www.example.com/pages/3.html
www.example.com/pages/4.html
Where I am having trouble is with finding a method to save the actual page. I was going to utilize a series of SendKeys to Alt, File, Saves As, Enter, etc.. but I figured there had to be some sort of object/method that could be used to do this more straight forward.
I've enabled the COM Internet Controls Reference and declared and new SHDoc.Vw.InternetExplorer and am able to programatically open the browser in a new window and navigate to the desired page(s). I've search online for a solution to the Save issue but have been unsuccessful. Does anyone have any ideas?

Dim baseUrl As String = "http://www.example.com/pages/{0}.html"
Dim basePath As String = "C:\some\path{0}.html"
Using ws As New System.Net.WebClient()
ForEach i As Integer In Enumerable.Range(1,4)
wc.DownloadFile(String.Format(baseUrl, i), string.Format(basePath, i))
Next i
End Using
If you have a lot of these want to get a little fancy, you can even use the DownloadFileAsync() method to queue up several of thses at once.

Related

Open another instance of Access using VBA

I am using a form in Access to open other access databases that perform various different queries that publish reports. As the databases I am opening use a multitude of tables, queries, and reports that have nothing to do with each other it would be awkward and time consuming to link to them all and tedious to make changes inside the original database.
I am using Dim appAccess As Access.Application to open each one. It creates a 2nd instance of the new accdb which will not become visible. However, if I go into view code in the original database and then go back to the form it opens the new instance perfectly visible and will continue to do so as long as I keep the original database open. If I close the original database and reopen it I have the same issue which can only be resolved by viewing the code again.
As an example of what I am using
Option Compare Database
Dim APP As Access.Application
Sub TEST()
Set APP = New Access.Application
APP.Visible = True
APP.OpenCurrentDatabase "C:\Users\Documents\Database1.accdb"
End Sub
Does anyone know why this is happening?
Add line:
APP.UserControl = True
For more info review https://www.devhut.net/2018/01/21/ms-access-VBA-open-another-database/#:~:text=MS%20Access%20VBA%20%E2%80%93%20Open%20Another%20Database%201,to%20truly%20appreciate%20the%20power%20of%20automation.%20

How to open an appication using VBScript within an HTA file

I am having trouble with getting the HTML Application (MeowcatSoftware Launcher Demo.HTA on GitHub) to open a target application, for example, MultiToolv0.2.exe. Is there a way to open the target applications such as the MultiTool using VBScript besides using Wscript.shell objects, which has been causing most of my problems?
I have tried the following, which didn't work:
Sub RunProgram
Set objShell = CreateObject(“Wscript.Shell”)
objShell.Run “notepad.exe c:\script\test.txt”
End Sub
(From 'Hey, Scripting Guy" Blog Post)
I played around a little with it but couldn't figure out how to achieve my goal. The blog post also mentioned using a Windows Shell object instead of Wscript.shell, but it looks like from the example that the Windows Shell object method was for opening a file using another program, and I just want it to simply open the target application. How do I open a program using VBScript within an HTA Application?
In vbscript this should work : You should using quote (") not like you posted in your question (“) and (”)
Call RunProgram()
Sub RunProgram
Set objShell = CreateObject("Wscript.Shell")
objShell.Run "notepad.exe c:\script\test.txt"
End Sub

How do I open Class Objects dynamically in the VBA editor without opening the editor?

Edit: See my comment below for a partial solution.
Edit 2: I found an adequate solution for closing the VBA editor but just want to see if anyone knows how to make it fully invisible the whole time. What I have found out works for my needs but I will leave this thread open for anyone who wants to elaborate on another method or expand on mine.
Original Post: I have a function that creates tables, queries, and forms. For the forms, it copies a template form and calls a function that replaces the forms VBA code dynamically. The below function I created works great, however, if I do not have the class object form open in the editor, I get the Run-Time error '2516': "Microsoft Access cannot find the module 'SPCInputFormVBA.', where SPCInputFormVBA is a variable for the class objects name. To further elaborate on the behavior, if I have the editor closed, as long as the module or class object is open in the editor, it will still work. I would like to be able to activate the corresponding Form_xyz object in the editor without the editor opening so that I can use this function to do a bunch of stuff to the to it. I am using a template form because there are a lot of things nested in it and has a lot going on. The form I can change the record source and various other things in it just fine, but the VBA portion is elusive to me so far. I thought opening the object would be easy but I am having a lot of trouble and cannot find a way to describe my issue that leads to me finding a solution.
Public Function InputFormVBA(SPCInputFormVBA)
DoCmd.OpenModule (SPCInputFormVBA)
Dim i As Integer
With Application.Modules(SPCInputFormVBA)
For i = 1 To .CountOfLines
If InStr(.Lines(i, 1), "TempTable") > 0 Then
' .ReplaceLine i, " If DCount( ""serial"", """ & tblName & """, _"
End If
'If Instr(.Lines(i, 1), "
Next i
End With
End Function
The closest I have come is trying various things with this function in order to help me understand how Access will react to different potential solutions I find online:
Sub PrintOpenModuleNames()
Dim i As Integer
Dim modOpenModules As Modules
Set modOpenModules = Application.Modules
For i = 0 To modOpenModules.Count - 1
Debug.Print modOpenModules(i).Name
'DoCmd.OpenModule (modOpenModules(i).Name)
Next
End Sub
The 'DoCmd.OpenModule (modOpenModules(i).Name) is commented out in this example and will open things, but it only sees things that are open already which doesn't help me. I do understand that there are different types of Modules, but I am not sure how to distinguish and the documentation online explains a general difference but doesn't reveal any way to reach out to the Class Object unless it is open in the editor already. Hoping someone can help or even correct my terminology if it is off and steer me to an existing solution elsewhere on the site.
You can use the VBE object model to access your form's code module without opening it in the VB Editor. And with that approach, the VB Editor window does not need to be visible or open. If the VB Editor window is not open, accessing the module this way will not open it. So I think that satisfies the main objective of your question.
It's possible to use VBE with late binding but, since you're not familiar with it, you'll likely want to use early binding instead. If so, add Microsoft Visual Basic for Applications Extensibility to your project's references.
Here is a very minimal procedure which only shows you how to reference a code module by name and print 2 of the module's properties.
Public Sub InputFormVBA(ByVal SPCInputFormVBA As String)
Dim objModule As CodeModule
Set objModule = Application.VBE.VBProjects(1).VBComponents(SPCInputFormVBA).CodeModule
With objModule
Debug.Print .CountOfDeclarationLines
Debug.Print .CountOfLines
End With
End Sub
If your database contains only one VBA project, VBProjects(1) will reference it. But if the db contains more than one VBA project, you may need to give VBProjects() a different number. I presume you'll figure that out pretty darn quick. :-)
A CodeModule object has methods you should find useful, including:
DeleteLines; Find; InsertLines; and ReplaceLine. However I don't really know what you want to do with the module's code, so will just leave it at that.

VBA File Open is slow

I'm trying to open a series of Excel spreadsheets using an instance of Excel created inside of a module in an Access database. I can get the files to open properly; however, the actual call to make Excel start takes quite a while, and to open the files takes even longer. The location of the files doesn't matter (same time to open on a local HDD as a network drive).
In an attempt to figure out what was taking so long, I added a timer to the logging module. Opening the files takes approximately 2m30s, during which the host application (Access) is entirely unresponsive to user input); the rest of the script executes in less than 10 seconds.
I'm using the standard Excel.Workbooks.Open call as follows
Set OpenSpreadsheet = Excel.Workbooks.Open(Name, 2, False)
Using Debug.Print methods around this line says it can take up to 2 1/2 minutes for this one line to execute.
Is there anything I can do to make the Excel files open quicker?
EDIT: When opening, UpdateLinks is False and ReadOnly is True; all other options are left to their defaults.
First idea: Can you use a jet driver with an ODBC connection to Excel, instead of opening it in an Excel object? Might be much faster.
Second idea: Make sure to create and instantiate the Excel application object just once at the beginning of the routine, then use the Excel.Workbooks.Open() and Excel.ActiveWorkbook.Close() for each spreadsheet. That way you're not "re-launching" the MS Excel application each time.
To draw out the second of #BradC's well-advised recommendations, if you need to use Excel in more than one procedure, create a self-initializing global function. I always use late binding for automating Office apps.
Public Function Excel(Optional bolCleanup As Boolean = False) As Object
Static objExcel As Object
If bolCleanup Then
If Not objExcel Is Nothing Then
Set objExcel = Nothing
Exit Function
End If
End If
If objExcel Is Nothing Then
Set objExcel = CreateObject("Excel.Application")
End If
Set Excel = objExcel
End Function
Then you can use it in code without needing to initialize it, and the single instance will remain available to any code that needs to use Excel.
And when you shut down your app, you'd call Excel(True) to clean up.
I do this with Outlook and Word all the time. However, there are some COM apps that it works poorly with, such as PDF Creator, which doesn't take kindly to this kind of treatment (you end up in an endless loop with it shutting down and re-initializing itself if you try to destroy the instance this way).
Another approach depends on your setup and process.
In my case I need read-only access to a set of Excel documents stored on SharePoint. My code is currently like:
For each path in paths
set wb = Workbooks.open(path,false)
next
In this case, each workbook is individually downloaded each time a workbook is opened. It would be significantly more efficient if the files were downloaded asyncronously and after the download is complete, the rest of the process executes on the local disk.
My idea is to use CopyFileEx() and pass a callback function. Excel will then download the Excel documents to disk asynchronously and call a VBA function regarding progress. When all files are completed, we can launch the next part of the process, which opens the local workbooks, scans them and then removes them from the local drive.
I'll post code later if I manage to implement it.

Issue with clipboard

I have a legacy app I am currently supporting that is having problems when people copy large quantities of data from a datasheet view.
The App is built in MS Access and the amount of rows being copied can get pretty large (sometimes in the thousands).
The funny thing about it is, you can paste the data out, but then Access keeps "rendering" the data into different formats and becomes CPU bound for LONG periods of time.
The Status message beside the progress bar at the bottom right of the MS Access Window is
Rendering Data to format: Biff5
Biff5 is a "Binary Interchange File Format (BIFF) version 5" According to Source
The app code doesn't use BIFF5 anywhere so I don't think this is an app problem.
I cannot find any data on this error anywhere on the web so I thought it would be a good question for stackoverflow.
So, can anyone help please?
Instead of trying to copy-paste, can't you just export the query to Excel?
I am not sure what the problem is but sometimes you can run into some very quirky bugs with Access. Have you tried running this on different machines? Different OS's? Would it be possible to paste the data into Excel and then import into Access using import functionality? Can you import the data directly instead of pasting it?
Try copy-paste operation through VBA:
Once user has selected data to copy, you can execute the code below when click on a button in the form, and then do a pastespecial in excel:
--- Data selected by user ---
RunCommand acCmdCopy
Dim xlApp As Object
Set xlApp = CreateObject(Class:="Excel.Application")
'New Excel Workbook
Dim xlWbook As Object 'Excel.Workbook
Set xlWbook = xlApp.Workbooks.Add
'Paste in excel
xlWSheet.Range("A1").Select
xlWSheet.PasteSpecial Link:=False, DisplayAsIcon:=False, Format:="Biff5"
We are all on the same OS here for this, I am investigating the possibility that some update sent out in the last maintenance window has caused this, as it wasn't a problem before this and there have been no new releases of the software in that time period.
Tried in on lot's of machines, same issue on them all.
The problem is actually with copying from a datasheet view in Access and pasting to Excel, not the other way around strangely.
Here is the use case
Access --> "Copy from datasheet"(Normal Ctrl+C) --> "paste into Excel" (Normal Ctrl+V)
(this works fine!)
When you then go back to Access to continue working, it is CPU bound doing the "Rendering Data to format: " thing, I mentioned above.
I'm stumped to be honest, it's all a bit strange.