How to log into AD while off the domain in Excel - excel

So I have been trying to add a level of security to a tool, and I came upon this post. The code works when on the network/domain, but I need to use this somehow for people who are remote and not on the network, nor vpn'ed into the network. Is this possible? I am learning as I go here, so this may not even be feasible in the first place. Just looking for every avenue possible.
Example Code:
Function WindowsLogin(ByVal strUserName As String, ByVal strpassword As String, ByVal strDomain As String) As Boolean
'Authenticates user and password entered with Active Directory.
On Error GoTo IncorrectPassword
Dim oADsObject, oADsNamespace As Object
Dim strADsPath As String
strADsPath = "WinNT://" & strDomain
Set oADsObject = GetObject(strADsPath)
Set oADsNamespace = GetObject("WinNT:")
Set oADsObject = oADsNamespace.OpenDSObject(strADsPath, strDomain & "\" & strUserName, strpassword, 0)
WindowsLogin = True 'ACCESS GRANTED
ExitSub:
Exit Function
IncorrectPassword:
WindowsLogin = False 'ACCESS DENIED
Resume ExitSub
End Function
EDIT: So #user2140261 told me about trying the LogonUser function from Advapi32.dll which looks like below:
Private Declare Function LogonUser Lib "advapi32.dll" Alias "LogonUserA" (ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, ByVal dwLogonType As UInteger, ByVal dwLogonProvider As UInteger, ByRef phToken As IntPtr) As Boolean
Sub LoginTest()
Dim logname As String
Dim logpass As String
Dim domainstring As String
logname = "username"
logpass = "password"
domainstring = "domain.com"
Call WindowsLogin(logname, logpass, domainstring)
End Sub
For some reason, this crashes Excel all together. Any reason why?

Not sure what the sub WindowsLogin is dong in your orininal code but try the below:
Private Declare Function LogonUser Lib "Advapi32" Alias "LogonUserA" _
(ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, _
ByVal dwLogonType As Long, ByVal dwLogonProvider As Long, phToken As Long) As Long
Public Sub LogUserOn()
Dim strUserName As String
Dim strPassword As String
Dim strDomain As String
Dim bResult As Boolean
strUserName = "UserName"
strPassword = "Password"
strDomain = "Domain"
bResult = LogonUser(strUserName, strDomain, strPassword, 2, 0, 0)
If bResult Then
MsgBox "Successfully Logged User In"
Else: MsgBox "And Error Occured While Trying To Log User In " & vbCrLf _
& "Error Code:" & Err.LastDllError
End If
End Sub

As the answer in the post you referenced said, if you just need to know who is using the app, and apply security based on that, then you can just get the logged-in user's name from the OS. The user can log into their laptop when disconnected using their cached credentials, and your app will be able to lock down whatever is needed based on the identity asserted by the OS.
Update
There are a couple of ways to get the User's identity from the OS. One is the environment variables %USERDOMAIN% and %USERNAME%. Another is the WScript.Network object:
Set WshNetwork = WScript.CreateObject("WScript.Network")
WScript.Echo "Domain = " & WshNetwork.UserDomain
WScript.Echo "User Name = " & WshNetwork.UserName
You can validate that the user is logged into the correct domain, and then provide access to the secured features.

Related

How to set focus and bring window of ThisWorkbook to front?

Purpose
Check for a numeric xls file on my desktop. If not found bring Thisworkbook to front.
Problem
If there are already opened workbooks, Thisworkbook remains in background. No error is raised.
Cause
Function Get_Highest_Numeric_Name although the function itself is working.
What I tried
Replacing ThisWorkbook.Activate with
AppActivate ((ThisWorkbook.Name) & " - Excel")
I got this error on AppActivate line
Invalid procedure call or argument
Strangely the error is not raised if I run that code from the code window.
Any way using AppActivate (ThisWorkbook.Name…) is not reliable, because it requires this option ( File name extension ) is enabled on Windows system.
Private Sub Workbook_Open()
If Len(Get_Highest_Numeric_Name("D:\Users\Waleed\Desktop\", "*.xls")) = 24 Then 'for Question on Stackoverflow
MsgBox "File not found", vbCritical + vbMsgBoxSetForeground, "File not found"
ThisWorkbook.Activate
Exit Sub
End If
End Sub
Function Get_Highest_Numeric_Name(strFold As String, Optional strext As String = "*.*") As String
Dim arrD, lastName As String, lngNb As Double, El
'Return all files name in an array
arrD = Split(CreateObject("wscript.shell").Exec("cmd /c dir """ & strFold & strext & """ /b").StdOut.ReadAll, vbCrLf)
If UBound(arrD) = -1 Then MsgBox "Nothing could be found in the path you supplied...": Exit Function
arrD(UBound(arrD)) = "####": arrD = Filter(arrD, "####", False) 'Remove the last (empty) element
For Each El In arrD 'iterate between the array elements
If IsNumeric(Split(El, ".")(0)) Then
'Compare the lngNb variable (initially 0) with the numeric value:
If lngNb < CDbl(Split(El, ".")(0)) Then
'addapt lngNb like the bigger number
lngNb = CDbl(Split(El, ".")(0)): lastName = El
End If
End If
Next
Get_Highest_Numeric_Name = strFold & lastName 'Build the necessary path
End Function
I tried hopelessly adding to the code an additional function API into a separate module ,to produce new message box with timeout.
Anyhow, using this API fixed the issue (I do not know why this happened).
Note: If I comment the line of MsgBoxTimeout, the problem error raising again.
Private Sub Workbook_Open()
If Len(Get_Highest_Numeric_Name("D:\Users\Waleed\Desktop\", "*.xls")) = 24 Then
Call MsgBoxTimeout(0, "File not found", "File not found", vbInformation + vbMsgBoxSetForeground, 0, 2000)
Exit Sub
End If
End Sub
'This function exists on a separate module
Public Declare Function MsgBoxTimeout Lib "user32" Alias "MessageBoxTimeoutA" ( _
ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, _
ByVal wType As VbMsgBoxStyle, ByVal wlange As Long, ByVal dwTimeout As Long) As Long

VBA Script to convert from internet explorer to Edge or chrome browser

I am looking for below code to covert from internet Explorer to Edge browser, request for you help to sort the same.
Sub CHECK_STATUS()
Dim cell As Range
Dim IntExp As Object
Set IntExp = CreateObject("InternetExplorer.Application")
IntExp.Visible = False
For Each cell In Range("A2:A20000")
'Here A2 is cell Address where we have stored urls which we need to test.
If Left(cell.Value, 4) = "http" Then
' Goto web page
IntExp.navigate cell.Text
' Below loop will run until page is fully loaded
Do While IntExp.Busy Or IntExp.readyState <> 4
DoEvents
Loop
' Now use text which you want to search , error text which you want to compare etc.
Dim ieDoc As Object
Set ieDoc = IntExp.document
If ieDoc.getElementsByClassName("box-content").Length <> 0 Then
cell.Offset(, 1).Value = ieDoc.getElementsByClassName("box-content")(0).innerText
End If
End If
Next cell
IntExp.Quit
Set IntExp = Nothing
End Sub
You need to use SeleniumBasic to automate Edge in VBA. SeleniumBasic is a Selenium based browser automation framework for VB.Net, VBA and VBScript.
I agree with QHarr's comments, you can also follow the steps below to automate Edge browser with SeleniumBasic:
Download the latest version of SeleniumBasic v2.0.9.0 from this link and install it.
Download the corresponding version of Edge WebDriver from this link.
Find the path of SeleniumBasic which is C:\Users\%username%\AppData\Local\SeleniumBasic in my computer (it might also be in this path C:\Program Files\SeleniumBasic), copy the Edge WebDriver msedgedriver.exe to this path.
Rename msedgedriver.exe to edgedriver.exe.
Open Excel and write the VBA code.
In the VBA code interface, click Tools > References, add Selenium Type Library reference and click OK to save.
I write a simple VBA code to show how to automate Edge using SeleniumBasic. You can refer to it and change the code according to your own demands:
Public Sub Selenium()
For Each cell In Range("A2:A20000")
Dim bot As New WebDriver
If Left(cell.Value, 4) = "http" Then
bot.Start "edge", cell.Value
bot.Get "/"
If Not bot.FindElementsByClass("box-content") Is Nothing Then
cell.Offset(, 1).Value = bot.FindElementsByClass("box-content")(1).Text
End If
End If
bot.Wait 3000
bot.Quit
Next cell
End Sub
I had been using IE and Internet Object Model (IOM) to achieve automation with internal web-based systems in my works at bank. Since the announcement that IE will be no longer supported by Microsoft at 15 June 2022, I started to look for possible alternative solutions on the internet.
After investigation, I found that there are two solutions to achieve automation on Edge browser : 1) SeleniumBasic or 2) Win API. Though SelenimBasic seems to be the mainstream suggestion at forums, Win API can be regarded as better solution in several different ways, especially for my own situations.
Pros of Win API Solution :
No need installation and regular update of Edge driver.
Able to automate with multiple existing Edge browser windows (which have been opened before program start).
Most of codes in existing IOM solution can be preserved and re-applied. It is because both solutions of IOM and Win API should use HTML Document Object Model (DOM) at last to achieve automation on webpage. The difference is on the way to find browser and attain HTMLDocument from browser.
Cons of Win API Solution :
We can automate with “webpage” on Edge browser but not the “Edge browser” itself. It is not like IOM and SeleniumBasic that can control web browser. For this, I use Shell function and DOS commands to achieve automation of opening and closing Edge browser.
The webpage has to be opened in IE mode at Edge browser which means this solution is subject to Microsoft’s future direction on IE mode of Edge browser.
Sharing on my experiences to use Win API on Edge browser webpage automation :
Place the following codes in a new blank module. I name this module as “MsEdge” usually. The codes in this module are no need to modify for usage. You can directly use the codes even if you don’t know much about Win API.
Public lngProcessID_Close As Long
'Part 1 --- Locate IES
Private strHwndIES As String
Private lngHwndIndex As Long
Private Declare Function EnumWindows Lib "user32" ( _
ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function EnumChildWindows Lib "user32" ( _
ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" ( _
ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
'Part 2 --- Get HTMLDocument from IES
Private Const SMTO_ABORTIFHUNG = &H2
Private Const GUID_IHTMLDocument2 = "{332C4425-26CB-11D0-B483-00C04FD90119}"
Private Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" ( _
ByVal lpString As String) As Long
Private Declare Function SendMessageTimeout Lib "user32" Alias "SendMessageTimeoutA" ( _
ByVal hWnd As Long, _
ByVal msg As Long, _
ByVal wParam As Long, _
lParam As Any, _
ByVal fuFlags As Long, _
ByVal uTimeout As Long, _
lpdwResult As Long) As Long
Private Declare Function IIDFromString Lib "ole32" ( _
lpsz As Any, lpiid As Any) As Long
Private Declare Function ObjectFromLresult Lib "oleacc" ( _
ByVal lResult As Long, _
riid As Any, _
ByVal wParam As Long, _
ppvObject As Any) As Long
'Part 3 --- Check Process Name
Private Declare Function GetWindowThreadProcessId Lib "user32" ( _
ByVal hWnd As Long, lpdwProcessId As Long) As Long
Public Function findEdgeDOM(Title As String, URL As String) As Object
'Find criteria-hitting Edge page in IE mode
Dim hwndIES As Long
Do
hwndIES = enumHwndIES
If hwndIES Then
Set findEdgeDOM = getHTMLDocumentFromIES(hwndIES)
If Not findEdgeDOM Is Nothing Then
If InStr(findEdgeDOM.Title, Title) * InStr(findEdgeDOM.URL, URL) Then
Do
hwndIES = enumHwndIES
Loop While hwndIES
Exit Function
Else
Set findEdgeDOM = Nothing
End If
End If
End If
Loop While hwndIES
End Function
Public Function enumHwndIES() As Long
'Get all hwnds of IES
If Len(strHwndIES) = 0 Then
EnumWindows AddressOf EnumWindowsProc, 0
lngHwndIndex = 0
End If
'Exit function when overflow
If lngHwndIndex + 1 > (Len(strHwndIES) - Len(Replace(strHwndIES, ",", ""))) Then
enumHwndIES = 0
strHwndIES = ""
Exit Function
End If
'Return IES hwnd one by one
enumHwndIES = CLng(Split(Left(strHwndIES, Len(strHwndIES) - 1), ",")(lngHwndIndex))
lngHwndIndex = lngHwndIndex + 1
End Function
Private Function EnumWindowsProc(ByVal hWnd As Long, ByVal lParam As Long) As Boolean
Dim lngProcessID As Long
GetWindowThreadProcessId hWnd, lngProcessID
EnumChildWindows hWnd, AddressOf EnumChildProc, lngProcessID
EnumWindowsProc = True
End Function
Public Function EnumChildProc(ByVal hWnd As Long, ByVal lParam As Long) As Boolean
Dim strTargetClass As String, strClassName As String
strTargetClass = "Internet Explorer_Server"
strClassName = getClass(hWnd)
If strClassName = strTargetClass Then
If GetObject("winmgmts:").ExecQuery("Select Name from Win32_Process WHERE ProcessId='" & lParam & "' AND Name='msedge.exe'").Count Then
strHwndIES = strHwndIES & hWnd & ","
lngProcessID_Close = lParam
EnumChildProc = False
Exit Function
End If
End If
EnumChildProc = True
End Function
Private Function getClass(hWnd As Long) As String
Dim strClassName As String
Dim lngRetLen As Long
strClassName = Space(255)
lngRetLen = GetClassName(hWnd, strClassName, Len(strClassName))
getClass = Left(strClassName, lngRetLen)
End Function
Public Function getHTMLDocumentFromIES(ByVal hWnd As Long) As Object
Dim iid(0 To 3) As Long
Dim lMsg As Long, lRes As Long
lMsg = RegisterWindowMessage("WM_HTML_GETOBJECT")
SendMessageTimeout hWnd, lMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, lRes
If lRes Then
IIDFromString StrPtr(GUID_IHTMLDocument2), iid(0)
ObjectFromLresult lRes, iid(0), 0, getHTMLDocumentFromIES
End If
End Function
Public Sub closeEdge(Title As String, URL As String)
'Close a Edge browser (the last one in EnumWindows order) with criteria-hitting webpage
lngProcessID_Close = 0
Dim findEdgeDOM As Object
Dim hwndIES As Long
Do
hwndIES = enumHwndIES
If hwndIES Then
Set findEdgeDOM = getHTMLDocumentFromIES(hwndIES)
If InStr(findEdgeDOM.Title, Title) * InStr(findEdgeDOM.URL, URL) Then
Shell "TaskKill /pid " & lngProcessID_Close
Do
hwndIES = enumHwndIES
Loop While hwndIES
Exit Sub
End If
End If
Loop While hwndIES
End Sub
Apply the functions in “MsEdge” module. There are a few application examples for you. Suggest to place and test below codes at another module:
Sub findEdgeDOM_DemoProc()
'Demo Proc : Use findEdgeDOM Function to get DOM of specific Edge webpage by Title AND URL
'Dim docHTML As MSHTML.HTMLDocument '--- Early Binding
Dim docHTML As Object '--- Late Binding
Set docHTML = findEdgeDOM("Enter Part of Webpage Title Here", "Enter Part of Webpage URL Here")
‘You can fill just one argument with either part of webpage title or URL as keyword to search for the target browser and leave another one blank (“”). If you provide both title and URL, the funcitons return DOM of the only browser that meets both criteria.
If Not docHTML Is Nothing Then Debug.Print docHTML.Title, docHTML.URL
End Sub
Sub goEdge()
'Go through every Edge webpage (opened in IE mode) and print out hwndIES, webpage Title & webpage URL
Dim hwndIES As Long
'Dim docHTML As MSHTML.HTMLDocument '--- Early Binding
Dim docHTML As Object '--- Late Binding
Do
hwndIES = enumHwndIES
If hwndIES Then
Set docHTML = getHTMLDocumentFromIES(hwndIES)
Debug.Print hwndIES, docHTML.Title, docHTML.URL
Else
Debug.Print "Procedure End"
End If
Loop While hwndIES
End Sub
Sub openEdgeByURL_DemoProc()
'Open Edge browser to specific URL
openEdgeByURL "Input Webpage URL Here"
End Sub
Public Sub openEdgeByURL(URL As String)
'Please change the path to your msedge.exe location in your PC
Shell "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe -url " & URL, vbNormalFocus
End Sub
Sub closeEdge_DemoProc()
'Close Edge browser
closeEdge "Enter Part of Webpage Title Here", "Enter Part of Webpage URL Here"
End Sub
Using a full heavy-weight web browser to do HTTP requests was probably a mistake in the first place, and now is a good opportunity to correct it.
Here is a stackoverflow question on how to do HTTP requests from Excel: it works the same in VBS.
getHTTP with (Excel) VBA?

Store name of email attachment - gives error on first run but works on second run

I'm attempting to open a dot .eml file stored locally and access the attachments file name with a excel macro.
I've gathered some code that does the job but not really. Opening the .eml file works (Set Myinspect = OL.ActiveInspector), but on the next line (Set MyItem = Myinspect.CurrentItem) I get the error "Run-time error '91' - Object variable or With block variable not set".
However if I re-run the code from the beginning after the first attempt (with the email now open from the last run), I get the name of the attachment without errors, and here naturally the first instance of the email closes and a second instance is opened. If I remove the line "MyItem.Close 1" I will have two instances of the email after the second run.
I suspected this might be due to that the email did not have time to open and load before the code tried to retrieve the name of the attachment, hence I tried to put a MsgBox before setting "Myitem" and wait until the email had loaded but that did not do the trick..
Appreciate any help that can be provided on this. The end use of the code is to loop through a list of .eml files to search for a .eml file with a attachment with a pre-determined name and then return the name of the .eml file, so since it loops a faster solution then "wait 5 seconds" for example would be optimal.
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As
Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal
lpDirectory As String, ByVal nShowCmd As Long) As Long
Private Const SW_SHOWNORMAL As Long = 1
Private Const SW_SHOWMAXIMIZED As Long = 3
Private Const SW_SHOWMINIMIZED As Long = 2
Sub test11()
strMyFile = "C:\test1.eml"
Dim Myinspect As Outlook.Inspector
Dim MyItem As Outlook.MailItem
Dim OL As Object
If Dir(strMyFile) = "" Then
MsgBox "File " & strMyFile & " does not exist"
Else
ShellExecute 0, "Open", strMyFile, "", "C:\test1.eml", SW_SHOWNORMAL
End If
Set OL = CreateObject("Outlook.Application")
Set Myinspect = OL.ActiveInspector
Set MyItem = Myinspect.CurrentItem
MsgBox "Attachment = " & MyItem.Attachments(1)
MyItem.Close 1
End Sub
Please, try replacing of:
ShellExecute 0, "Open", strMyFile, "", "C:\test1.eml", SW_SHOWNORMAL
with
Const waitOnReturn as boolean = True
VBA.CreateObject("WScript.Shell").Run """" & strMyFile & """", 1, waitOnReturn
This version will wait for the application to open the file. At least, theoretically...:) And no need of any API.
Please, send some feedback after testing it.
You are getting that error because you need to give enough time for the reading pane to become visible. Is this what you are trying?
Option Explicit
Private Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hwnd As LongPtr, ByVal lpOperation As String, ByVal lpFile As String, _
ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As LongPtr
Private Const SW_SHOWNORMAL As Long = 1
Private Const strMyFile As String = "C:\test1.eml"
Dim Retry As Long
Sub Sample()
Dim Myinspect As Outlook.Inspector
Dim MyItem As Outlook.MailItem
Dim OL As Object
If Dir(strMyFile) = "" Then
MsgBox "File " & strMyFile & " does not exist"
Exit Sub
Else
ShellExecute 0, "Open", strMyFile, "", strMyFile, SW_SHOWNORMAL
End If
Set OL = CreateObject("Outlook.Application")
Set Myinspect = OL.ActiveInspector
'~~> Wait till the reading pane is visible
Do While TypeName(Myinspect) = "Nothing"
'~~> Wait for 1 sec
Wait 1
Set Myinspect = OL.ActiveInspector
'~~> After 10 retries, stop retrying
If Retry > 10 Then Exit Do
Loop
If TypeName(Myinspect) = "Nothing" Then
MsgBox "Unable to get the Outlook Inspector"
Exit Sub
End If
Set MyItem = Myinspect.CurrentItem
MsgBox "Attachment = " & MyItem.Attachments(1)
MyItem.Close 1
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer: DoEvents: Wend
Retry = Retry + 1
End Sub
Note: Instead of Do While TypeName(Myinspect) = "Nothing", you can also use Do While Myinspect Is Nothing
'~~> Wait till the reading pane is visible
Do While Myinspect Is Nothing
'~~> Wait for 1 sec
Wait 1
Set Myinspect = OL.ActiveInspector
'~~> After 10 retries, stop retrying
If Retry > 10 Then Exit Do
Loop
If Myinspect Is Nothing Then
MsgBox "Unable to get the Outlook Inspector"
Exit Sub
End If
Opening and showing an EML file to an end-user when all you want is the attachment name may or may not be what the user expects.
I am not aware of any libraries that would let you open EML files directly from VBA, but if using Redemption (I am its author) is an option, you can create a temporary MSG file and import the EML file. You can then access the message without showing it to the user. Something along the lines
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = OutlookApplication.Session.MAPIOBJECT
set Msg = Session.CreateMessageFromMsgFile("c:\temp\test.msg")
Msg.Import "c:\temp\test.eml", 1031
Msg.Save
for each attach in Msg.Attachments
MsgBox attach.FileName
next

Download file from url in Excel 2019 (it works on Excel 2007)

I got a code to download a CSV file from a website that requires credentials. I got a code thanks to this website and I could adapted to my needs. My relevant part of code is:
Option Explicit
Private Declare Function URLDownloadToFileA Lib "urlmon" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
Private Function DownloadUrlFile(URL As String, LocalFilename As String) As Boolean
Dim RetVal As Long
RetVal = URLDownloadToFileA(0, URL, LocalFilename, 0, 0)
If RetVal = 0 Then DownloadUrlFile = True
End Function
Sub DESCARGAR_CSV_DATOS()
Dim EstaURL As String
Dim EsteCSV As String
EstaURL = "https://user:token#www.privatewebsite.com/export/targetfile.csv"
EsteCSV = "MyCSV " & Format(Date, "dd-mm-yyyy") & ".csv"
With Application
.DisplayAlerts = False
.ScreenUpdating = False
End With
DownloadUrlFile EstaURL, _
ThisWorkbook.Path & "\" & EsteCSV
DoEvents
Workbooks.Open ThisWorkbook.Path & "\" & EsteCSV, , True, , , , , , , , , , , True
'rest is just doing operations and calculations inside workbook
End Sub
Sorry but I cannot provide the real url. Anyways, this code has been working perfectly since September 2019. And it still works perfectly every day.
The computers that execute this code are all of them Windows 7 and Excel 2007 and 64 bits. None of them fail.
But now, this task is going to be outsourced to another office. There, the computers are Excel 2019, Windows 10 and 64 bits.
And the code does not work there. It does not arise any error, but the function DownloadUrlFile does not download any file on Excel 2019 + W10
So to resume, Excel 2007 + Windows 7 works perfectly (tested today). Excel 2019 + Windows 10 does not work (no errors on screen).
Things I've tried to fix it:
I've checked that file urlmon.dll exists in system32 and it does
I've tried declaring the function URLDownloadToFileA using PtrSafe
If I manually type the url in Chrome in the PC with Excel 2019 + W10, the file is downloaded properly, so the URL is ok.
None of this solved my problem. I'm pretty sure the solution it's really easy, because the code works perfectly in Excel 2007, but I can't find what I'm missing here.
I would like to get a code that works in any case, but I would accept also a solution that works only in Excel 2019 and Windows 10 if it's the only way.
Hope somebody can throw some light about this. Thanks in advance.
UPDATE: Tried also the solution in this post but still nothing.
UPDATE 2: Also, tested the code posted here (Excel 2007) with Excel 2010 and it works perfectly.
UPDATE 3: The variable RetVal stores the result of the download. I know some values:
' Returns 0 if success, error code if not.
' Error codes:
' -2146697210 "file not found".
' -2146697211 "domain not found".
' -2147467260 "transfer aborted".
But in my case, it returns -2147221020. What could that be?
UPDATE 4: Well, this is just weird. I've tried same code to download a different file from a public website, and it works in Excel 2019 + W10.
I made a new easy code like this:
#If VBA7 And Win64 Then
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
Alias "URLDownloadToFileA" ( _
ByVal pCaller As LongPtr, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As LongPtr, _
ByVal lpfnCB As LongPtr _
) As Long
#Else
Private Declare Function URLDownloadToFile Lib "urlmon" _
Alias "URLDownloadToFileA" ( _
ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long _
) As Long
#End If
Sub Descarga()
Dim EstaURL As String
Dim EsteCSV As String
EstaURL = privateone 'can't be shared, sorry
EsteCSV = "CSV Datos " & Format(Date, "dd-mm-yyyy") & ".csv"
On Error GoTo Errores
URLDownloadToFile 0, "https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm", ThisWorkbook.Path & "\" & EsteCSV, 0, 0
URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" & EsteCSV, 0, 0
Exit Sub
Errores:
'Si es un bucle lo mejor sería no mostrar ningún mensaje
MsgBox "Not downloaded", vbCritical, "Errores"
End Sub
The line that says URLDownloadToFile 0, "https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm", ThisWorkbook.Path & "\" & EsteCSV, 0, works perfect and downloas the file.
The line URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" & EsteCSV, 0, 0does not work.
So tested again exactly same code but on Excel 2007 and both of them work
Why the first download works and the second one does not on Excel 2019 + W10 but both of them work on Excel 2007+W7?
UPDATE 5: The URL is private, because it contains username and password, but it's like this:
https://user:token#www.privatewebsite.com/export/target%20file.csv
Thanks to #Stachu, the URL does not work manually on Internet Explorer on any PC (copy/pasting in the explorer navigation bar, I mean). The URL works perfectly in Google Chrome in all PC.
It's really curious that, manually, the URL on Internet Explorer does not work, but same URL coded with VBA and Executed on Excel2007/2010 works perfectly. Maybe I should change something about the encoding?
UPDATE 6: Still studying all posts by you. The issue here is that I'm just the data guy, the analyst, so plenty of information posted here sounds really hardcore to me.
I've emailed all the info to the IT guys 1 day ago, and still waiting for an answer.
Meanwhile, and based on information here, finally coded something totally different that works on all machines. It works on Windows 7 and 10, and it works on Excel 2007 and 2010 (installed as 32 bits) and Excel 2019 (installed as 64 bits).
I'm adding the code here, so maybe somebody can explain why it works properly, but it looks like the issue was the base64 encoding.
The code I got now is like this (added reference to Microsoft Winhttp Setvices 5.1)
Application.ScreenUpdating = False
Dim whr As WinHttp.WinHttpRequest
Dim oStream As Object
Dim EsteCSV As String
Dim EstaURL As String
EstaURL = "https://user:pass#www.privatewebsite.com/export/target%20file.csv"
EsteCSV = "CSV Datos" & Format(Date, "dd-mm-yyyy") & ".csv"
'Set whr = CreateObject("WinHttp.WinHttpRequest.5.1")
Set whr = New WinHttp.WinHttpRequest
whr.Open "GET", EstaURL, True
whr.setRequestHeader "Authorization", "Basic " & EncodeBase64("user" & ":" & "password")
whr.send
' Using 'true' above and the call below allows the script to remain responsive.
whr.waitForResponse
DoEvents
Set oStream = CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
oStream.Write whr.responseBody
oStream.SaveToFile ThisWorkbook.Path & "\" & EsteCSV
oStream.Close
DoEvents
Set oStream = Nothing
whr.abort
Set whr = Nothing
'rest of code for operations
Kill ThisWorkbook.Path & "\" & EsteCSV
Application.ScreenUpdating = True
End Sub
Private Function EncodeBase64(text As String) As String
Dim arrData() As Byte
arrData = StrConv(text, vbFromUnicode)
Dim objXML As Object
Dim objNode As Object
Set objXML = CreateObject("MSXML2.DOMDocument")
Set objNode = objXML.createElement("b64")
objNode.DataType = "bin.base64"
objNode.nodeTypedValue = arrData
EncodeBase64 = objNode.text
Set objNode = Nothing
Set objXML = Nothing
End Function
Sub Code is fine. Check the references in tools menu in vba and make declaration ptrsafe as below
Private Declare PtrSafe Function URLDownloadToFileA Lib "urlmon" _
The computers that execute this code are all of them Windows 7 and
Excel 2007 and 64 bits. None of them fail.
But now, this task is going to be outsourced to another office. There,
the computers are Excel 2019, Windows 10 and 64 bits.
And the code does not work there. It does not arise any error, but the
function DownloadUrlFile does not download any file on Excel 2019 +
W10
I'm guessing it is not working in another office.
This will only happen if the URL is private and the IPs are not whitelisted. You can check with your networking team for the same whether they have whitelisted the IPs for that URL.
The line that says URLDownloadToFile 0,
"https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm",
ThisWorkbook.Path & "\" & EsteCSV, 0, works perfect and downloas the
file.
The line URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" &
EsteCSV, 0, 0does not work.
So tested again exactly same code but on Excel 2007 and both of them
work
Why the first download works and the second one does not on Excel 2019
+ W10 but both of them work on Excel 2007+W7?
Also, It makes no sense that the same code is working perfectly fine for the public URL and not for the private URL except there is an IP restriction.
As for your error, -2147221020 => 0x800401E4 as per VBA Error Codes and Descriptions
this error is MK_E_SYNTAX which is 'invalid moniker syntax'.
When it says moniker I guess it means your url and to be honest the web address does not look syntactically correct...
"https://user:token#www.privatewebsite.com/export/targetfile.csv"
I'd have to dig around to see if that truly met the web standard for a url. In the meantime I'd suggest figuring out a different url. It maybe that an upgrade to urlmon.dll now complains about the url whereas the Windows 7 version didn't.
Ok, my bad, actually it looks like you can do such uris, in theory, so I have a uri fragment
first-client:noonewilleverguess#localhost:8080/oauth/token taken from OAuth2 Boot
Ok, so it is valid, re top of page 17 rfc3986.
authority = [ userinfo "#" ] host [ ":" port ]
Looks like you'll have to drop into Windows API calls to set username and password. So here is sample code
Option Explicit
'* with thanks to http://www.holmessoft.co.uk/homepage/WininetVB.htm
Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _
ByVal lpszAgent As String, _
ByVal dwAccessType As Long, _
ByVal lpszProxyName As String, _
ByVal lpszProxyBypass As String, _
ByVal dwFlags As Long) As Long
Private Enum InternetOpenAccessTypes
INTERNET_OPEN_TYPE_PRECONFIG = 0 'Retrieves the proxy or direct configuration from the registry.
INTERNET_OPEN_TYPE_DIRECT = 1 'Resolves all host names locally.
INTERNET_OPEN_TYPE_PROXY = 3 'Passes requests to the proxy unless a proxy bypass list is supplied and the name to be resolved bypasses the proxy. In this case, the function uses INTERNET_OPEN_TYPE_DIRECT.
INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4 'Retrieves the proxy or direct configuration from the registry and prevents the use of a startup Microsoft JScript or Internet Setup (INS) file.
End Enum
Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
ByVal hInternetSession As Long, _
ByVal lpszServerName As String, _
ByVal nServerPort As Integer, _
ByVal lpszUsername As String, _
ByVal lpszPassword As String, _
ByVal dwService As Long, _
ByVal dwFlags As Long, _
ByVal dwContext As Long) As Long
Private Const INTERNET_SERVICE_HTTP = 3
Private Const INTERNET_FLAG_NO_COOKIES = &H80000
Private Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000
Private Declare Function HttpOpenRequest Lib "wininet.dll" Alias "HttpOpenRequestA" ( _
ByVal hHttpSession As Long, _
ByVal lpszVerb As String, _
ByVal lpszObjectName As String, _
ByVal lpszVersion As String, _
ByVal lpszReferer As String, _
ByVal lpszAcceptTypes As String, _
ByVal dwFlags As Long, _
ByVal dwContext As Long) As Long
Private Declare Function HttpSendRequest Lib "wininet.dll" Alias "HttpSendRequestA" ( _
ByVal hHttpRequest As Long, _
ByVal lpszHeaders As String, _
ByVal dwHeadersLength As Long, _
ByVal lpOptional As String, _
ByVal dwOptionalLength As Long) As Boolean
Private Declare Function InternetReadFile Lib "wininet.dll" ( _
ByVal hFile As Long, _
ByVal lpBuffer As String, _
ByVal dwNumberOfBytesToRead As Long, _
ByRef lpNumberOfBytesRead As Long) As Boolean
Private Sub Test()
Dim hInternet As Long
hInternet = InternetOpen("Mozilla", INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
If hInternet = 0 Then
Debug.Print "InternetOpen failed"
GoTo SingleExit
End If
Dim sUSERNAME As String
sUSERNAME = "foo"
Dim sPASSWORD As String
sPASSWORD = "bar"
Dim hConnect As Long
hConnect = InternetConnect(hInternet, "www.microsoft.com", 80, sUSERNAME, sPASSWORD, INTERNET_SERVICE_HTTP, 0, 0)
If hConnect = 0 Then
Debug.Print "InternetConnect failed"
GoTo SingleExit
End If
Dim lFlags As Long
Dim hRequest As Long
lFlags = INTERNET_FLAG_NO_COOKIES
lFlags = lFlags Or INTERNET_FLAG_NO_CACHE_WRITE
hRequest = HttpOpenRequest(hConnect, "GET", "www.microsoft.com", "HTTP/1.0", vbNullString, vbNullString, lFlags, 0)
Dim bRes As Boolean
bRes = HttpSendRequest(hRequest, vbNullString, 0, vbNullString, 0)
Dim strFile As String
strFile = "downloadedfile.txt"
Dim strBuffer As String * 1
Dim strDir As String
strDir = Dir(ThisWorkbook.Path & "\" & strFile)
If Len(strDir) > 0 Then
Kill ThisWorkbook.Path & "\" & strFile
End If
Dim iFile As Long
iFile = FreeFile()
Open ThisWorkbook.Path & "\" & strFile For Binary Access Write As iFile
Do
Dim lBytesRead As Long
bRes = InternetReadFile(hRequest, strBuffer, Len(strBuffer), lBytesRead)
If lBytesRead > 0 Then
Put iFile, , strBuffer
End If
Loop While lBytesRead > 0
Debug.Print "finished"
SingleExit:
End Sub
UPDATE: Congratulations on your solution for which you invite an explanation, perhaps see this MSDN Forum where the discourse outlines the different technology stacks. If I browse the C++ header file urlmon.h then URLDownloadToFile looks like its based on WinInet. So switching to WinHTTP is a smart move to a more server based stack.
Also, on the same stack logic, I believe you could have used MSXML2.ServerXMLHTTP see this VBScript newsgroup archive
I tried this solution in Excel 2019 / O365 64 bits (version: 1912) / win 10 64 bits
I know you have a working code, but if anybody else needs an alternative, here it is:
Sub DownloadFile()
Dim evalURL As String
Dim streamObject As Object
Dim winHttpRequest As Object
Set winHttpRequest = CreateObject("Microsoft.XMLHTTP")
evalURL = "https://fullPathTofile/tst.csv" ' -> Didn't need to add the username at the beginning
winHttpRequest.Open "GET", evalURL, False, "username", "password"
winHttpRequest.send
If winHttpRequest.Status = 200 Then
Set streamObject = CreateObject("ADODB.Stream")
streamObject.Open
streamObject.Type = 1
streamObject.Write winHttpRequest.responseBody
streamObject.SaveToFile "C:\temp\file.csv", 2 ' 1 = no overwrite, 2 = overwrite
streamObject.Close
End If
End Sub
The "simplified" method you used (user+password#url) has had spotty support at best due to its potential of remote abuse. Some browsers no longer even support it.
For example, a HREF link of ...admin:admin#192.168.1.1/cgi-bin/something-else?... is enough to exploit several routers protected only by a "deny remote access" default instead of a reliable password, and there are many of those.
You might be able to overcome this by saving user and password in Internet Explorer, whose libraries are used by Excel, and/or placing the remote site in the "Trusted Sites" group from Internet Options. But this is a stopgap measure too, since the password cache might be erased by accident and security levels might be reset by an update at any time (I had this happen to me more than once).
Here there are other methods discussed. Otherwise, your solution of course works (you might want to add an answer to that effect, and mark it accepted for the next who gets the same problem).
I was struggling for days with this, until I figured out it can be done in one line of PowerShell, for example:
invoke-webrequest -Uri "http://myserver/Reports/Pages/ReportViewer.aspx?%2fClients%2ftest&rs:Format=PDF&rs:ClearSession=true&CaseCode=12345678" -OutFile "C:\Temp\test.pdf" -UseDefaultCredentials
I looked into doing it purely in VBA but it runs to several pages and was making me lose me mind, so I just call my PowerShell script from VBA every time I want to download a file.
very, very simple and the "UseDefaultCredentials" works an absolute treat and I don't have to worry about logging into the remote site etc.
This lets me download reports from SSRS in PDF format to a folder in a couple of lines of code.
Thanks everybody for all your help and answers. Unfortunately, my IT department was not able to tell me what was happening exactly, even with all the links provided here with a lot of useful info.
I'm posting here the code we are using here right now. IT's works perfectly on Excel 2007 32 bit, Excel 2010 32 and 64 bits and Excel 2019 64 bits. It works too on Windows 7 and 10.
To make this code work, you need to add a reference to Microsoft Winhttp Setvices 5.1. Check How to Add an Object Library Reference in VBA in case you don't know how to do this:
Application.ScreenUpdating = False
Dim whr As WinHttp.WinHttpRequest
Dim oStream As Object
Dim EsteCSV As String
Dim EstaURL As String
EstaURL = "https://user:pass#www.privatewebsite.com/export/target%20file.csv"
EsteCSV = "CSV Datos" & Format(Date, "dd-mm-yyyy") & ".csv"
'Set whr = CreateObject("WinHttp.WinHttpRequest.5.1")
Set whr = New WinHttp.WinHttpRequest
whr.Open "GET", EstaURL, True
whr.setRequestHeader "Authorization", "Basic " & EncodeBase64("user" & ":" & "password")
whr.send
' Using 'true' above and the call below allows the script to remain responsive.
whr.waitForResponse
DoEvents
Set oStream = CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
oStream.Write whr.responseBody
oStream.SaveToFile ThisWorkbook.Path & "\" & EsteCSV
oStream.Close
DoEvents
Set oStream = Nothing
whr.abort
Set whr = Nothing
'rest of code for operations
Kill ThisWorkbook.Path & "\" & EsteCSV
Application.ScreenUpdating = True
End Sub
Private Function EncodeBase64(text As String) As String
Dim arrData() As Byte
arrData = StrConv(text, vbFromUnicode)
Dim objXML As Object
Dim objNode As Object
Set objXML = CreateObject("MSXML2.DOMDocument")
Set objNode = objXML.createElement("b64")
objNode.DataType = "bin.base64"
objNode.nodeTypedValue = arrData
EncodeBase64 = objNode.text
Set objNode = Nothing
Set objXML = Nothing
End Function
Thanks again everyone. SO is a great place.

VBA - Username of open workbook (read only)

If an open workbook (located on a server) is in read only mode, how can I display the active username using VBA?
I've looked into .WriteReservedBy but this only shows the name of the person that last saved the file with a password.
This should probably be a comment but my reputation is too low
I've seen this but never needed the info...
Things to try:
ThisWorkbook.UserStatus - array with all current users for the file open as exclusive or shared
Environ("USERNAME")
CreateObject("WScript.NetWork").UserName
API calls:
.
Declare Function WNetGetUser Lib "mpr.dll" Alias "WNetGetUserA"
( _
ByVal lpName As String, _
ByVal lpUserName As String, _
lpnLength As Long
) As Long
Declare Function GetUserName& Lib "advapi32.dll" Alias "GetUserNameA" _
(ByVal lpBuffer As String, nSize As Long)
.
more details about these APIs:
https://support.microsoft.com/en-us/kb/161394
http://www.vbaexpress.com/kb/getarticle.php?kb_id=768
WMI Win32_NetworkConnection:
Public Function GetActiveUser(Optional ByVal computer As String = ".") As String
Dim wmi As Object, itm As String
On Error Resume Next
Set wmi = GetObject("winmgmts:\\" & computer & "\Root\CIMv2")
itm = wmi.ExecQuery("Select UserName from Win32_NetworkConnection", , 48)
GetNetActiveUser = itm
End Function

Resources