Shell console command in VBA Access doesn't work properly - vba

I have a big issue with my code, which i'vre created by using already known/someone solution.
What's going on:
I need a code which will give me an extract of all files, which lastDateModified is older than some specific date. However the best solution will be if i will received those file names in array (don't know how to do that"
Problem:
When I enter a command in the console, it gives me the list of files correctly.
Whereas when I place it in Access it gets me text:
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
I:\Documents\Access>
Code:
Public Sub TestCommandLine()
Const lngCancelled_c As Long = 0
Dim strCmd As String
strCmd = "cmd.exe forfiles /P directory /S /D +01/04/2015) > directory2"
CommandLine strCmd, False
End Sub
Public Function CommandLine(command As String, Optional ByVal keepAlive As _
Boolean = False, Optional windowState As VbAppWinStyle =
VbAppWinStyle.vbHide) _
As Boolean
'--------------------------------------------------------------------------------
' Procedure : CommandLine
' Author : Aaron Bush (Oorang)
' Date : 10/02/2007
' Purpose : Provides a simple interface to execute a command lines from VBA.
' Input(s) :
' command : The DOS command you wish to execute.
' keepAlive : Keeps the DOS window open *after* command has been
' executed. Default behavior is to auto-close. (See
' remarks section for additional information.)
' windowState : Determines the window state of the DOS prompt
' *during* command execution.
' Output : True if completed with no errors, False if error encountered.
' Remarks : If the windowState property is set to vbHide while the keepAlive
' parameter is set to True, then windowState will be changed to
' vbNormalFocus.
'--------------------------------------------------------------------------------
On Error GoTo Err_Hnd
Const lngMatch_c As Long = 0
Const strCMD_c As String = "cmd.exe"
Const strComSpec_c As String = "COMSPEC"
Const strTerminate_c As String = " /c "
Const strKeepAlive_c As String = " /k "
Dim strCmdPath As String
Dim strCmdSwtch As String
If keepAlive Then
If windowState = vbHide Then
windowState = vbNormalFocus
End If
strCmdSwtch = strKeepAlive_c
Else
strCmdSwtch = strTerminate_c
End If
strCmdPath = VBA.Environ$(strComSpec_c)
If VBA.StrComp(VBA.Right$(strCmdPath, 7), strCMD_c, vbTextCompare) <> _
lngMatch_c Then
strCmdSwtch = vbNullString
End If
VBA.Shell strCmdPath & strCmdSwtch & command, windowState
CommandLine = True
VBA.Shell Nothing
Exit Function
Err_Hnd:
CommandLine = False
End Function
Do anyone have this issue?

Using this construct
Option Explicit
Public Sub Find_Files()
Dim fileDetails() As String
fileDetails = Split(CreateObject("wscript.shell").exec("cmd /c forfiles /P C:\Users\User\Desktop\TestFolder /S /D -19/04/2018").StdOut.ReadAll, vbCrLf)
Dim i As Long
For i = LBound(fileDetails) To UBound(fileDetails)
If Not IsEmpty(fileDetails(i)) Then Debug.Print fileDetails(i)
Next i
End Sub

Another question abt same problem - how can i check all files in network drive?
i've tried with
cmd /c pushd "network drive path" forfiles /S /D +14/04/2018
and it doesnt work, however when i write it in console in seperate lines
pushd "network_drive"
forfiles /s /d +10/04/2018
popd
then all works.
Any ideas?
SOLVED:
fileDetails = Split(CreateObject("wscript.shell").exec("cmd /c pushd " & Chr(34) & IMPORT_PATH & FOLDER_PATH & Chr(34) & " & forfiles /S /D +" & s_date & " & popd").StdOut.ReadAll, Chr(10))
And it works for net drive

Related

Check right installation of 7-Zip (64 / 32bit)

My goal is avoid error msg from VBA debugger. Need to check which version of 7-zip have installed, program files/ or program files (x86):
Trying do simple "IF" function.
Dim PathZipProgram As String
strCommand As String
PathZipProgram = "C:\Program Files(x86)\7-Zip\7z.exe"
If Right(PathZipProgram, 1) Then
PathZipProgram = PathZipProgram
Else
PathZipProgram = "C:\Program Files\7-Zip\7z.exe"
End If
Shell strCommand
strCommand = """" & PathZipProgram & """ a -tzip """
VBA cant find 7zip.
You can check if the file exist with a function like this:
Function FileExists(FilePath As String) As Boolean
Dim TestStr As String
TestStr = ""
On Error Resume Next
TestStr = Dir(FilePath)
On Error GoTo 0
If TestStr = "" Then
FileExists = False
Else
FileExists = True
End If
End Function
And then use it in your code:
Dim PathZipProgram As String
Dim strCommand As String
PathZipProgram = "C:\Program Files(x86)\7-Zip\7z.exe"
If Not FileExists(PathZipProgram) Then
PathZipProgram = "C:\Program Files\7-Zip\7z.exe"
End If
Shell strCommand
strCommand = """" & PathZipProgram & """ a -tzip """
Hope this help as a starting point.
Installation Path: 7-Zip could be installed in another disk location (for example the D: drive, or somewhere else). You do
need to read the paths from the registry to be sure. A couple of
suggestions below:
1. Hacky Version
I have put a full script below to use, but you can essentially get what you want the hacky way (make sure the paths actually exists - obviously - maybe use "C:\Program Files\7-Zip" if there are problems seen):
Check File Exists:
Set fso = CreateObject("Scripting.FileSystemObject")
If (fso.FileExists("C:\Program Files (x86)\7-Zip\7z.exe")) Then
' Do Stuff
End If
Get File Version:
Set fso = CreateObject("Scripting.FileSystemObject")
MsgBox fso.GetFileVersion("C:\Program Files (x86)\7-Zip\7z.exe")
Don't rely on this please. It will fail eventually. Please see below.
2. Full Version
Here is a full version, the steps you need to make something that has a hope to be reliable. Essentially read paths from registry and take it from there:
Const HKEY_LOCAL_MACHINE = &H80000002 : strComputer = "."
Set fso = CreateObject("Scripting.FileSystemObject")
Set reg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\default:StdRegProv")
' 64-bit - Read 7-zip installation path from registry
regpath64 = "SOFTWARE\7-Zip"
reg.GetStringValue HKEY_LOCAL_MACHINE, regpath64, "Path64", regvalue64
fullpath64 = regvalue64 + "\" + "7z.exe"
If (fso.FileExists(fullpath64)) Then
MsgBox "7-zip 64-bit: " + fso.GetFileVersion(fullpath64), vbOKOnly, "64-bit:"
End If
' 32-bit - Read 7-zip installation path from registry
regpath32 = "SOFTWARE\WOW6432Node\7-Zip"
reg.GetStringValue HKEY_LOCAL_MACHINE, regpath32, "Path", regvalue32
fullpath32 = regvalue32 + "\" + "7z.exe"
If (fso.FileExists(fullpath32)) Then
MsgBox "7-zip 32-bit: " + fso.GetFileVersion(fullpath32), vbOKOnly, "32-bit:"
End If
Disclaimer: For the 64-bit registry read: there could be a Path and a Path64 entry
(I have both). Not sure what earlier and / or later versions will
have. Please check.

VBA execute function with several quotation marks in parameters cmd.exe

Here is a syntax error. How to properly run the ShellWait function? I care about double quotes being used. The boolean parameter should be able to be changed to false. I do not use VBA every day so I am asking for an example if you can.
Sub ShellWait(fName As String, Optional showWindow As Boolean = True)
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
wsh.Run fName, -showWindow, True
End Sub
Private Sub Document_close()
ShellWait "cmd /c echo ^<meta http-equiv="x-ua-compatible" content="IE=10"^> && echo '"hello"' "
' ^the above code does not work
ShellWait "cmd /c echo '"hello"' "
' ^the above code does not work, syntax error
ShellWait (cmd /c echo '"hello"')
' ^the above code does not work, syntax error
End Sub
You could use the following
ShellWait "cmd /K echo ^<meta http-equiv=""x-ua-compatible"" content=""IE=10""^> && echo '""hello""' "

Can't run DIR from WScript Shell in VBA?

I use the following function in a lot of my VBA projects. I initially added the reference to Windows Script Host Object model to take advantage of Intellisense, but then switched to late binding so I didn't have to reference a bunch of stuff.
Private Function RunCMD(ByVal strCMD As String) As String
'Runs the provided command
Dim oShell As Object 'New WshShell
Dim cmd As Object 'WshExec
Dim x As Integer
Const WshRunning = 0
On Error GoTo wshError
x = 0
RunCMD = "Error"
Set oShell = CreateObject("Wscript.Shell")
Set cmd = oShell.Exec(strCMD)
'Debug.Print strCMD
'Stop
Do While cmd.Status = WshRunning
Sleep 100 'for 1/10th of a second
x = x + 1
If x > 1200 Then 'We've waited 2 minutes so kill it
cmd.Terminate
MsgBox "Error: Timed Out", vbCritical, "Timed Out"
End If
Loop
RunCMD = cmd.StdOut.ReadAll & cmd.StdErr.ReadAll
Set oShell = Nothing
Set cmd = Nothing
Exit Function
wshError:
On Error Resume Next
RunCMD = cmd.StdErr.ReadAll
Resume Next
End Function
It works great when you do something like
RunCMD("ping www.bing.com") or
RunCMD("winrs -r:" & strHost & " reg query hklm\system\currentcontrolset\services\cdrom /v start")
However RunCMD("Dir c:\config* /a:-d /b /d /s") fails, and cmd.StdErr.ReadAll gives an Object Variable or With Block not set error. Even a simple RunCMD("Dir") fails.
Why does DIR make the WScript shell crap out? More importantly, how can I use CMD's DIR function (not VBA's DIR function!) to get a list of files that match a search pattern?
Does it work if you preface your dir command with "cmd /c " and wrap your DOS command in double quotes, like
RunCmd("cmd /c ""DIR""")
or
RunCmd("cmd /c ""Dir c:\config* /a:-d /b /d /s""")

Get logged on username in Excel VBA - not the account running Excel (using RunAs)

I have logged into my workstation with my normal domain credentials. We shall call this AccountA.
I then use the "run as a different user" to launch Excel. We shall call this AccountB. I do this because the permissions needed to query some SQL servers must be done using AccountB.
At this point, I need to include a subroutine to launch a Shell to create directories and move files on a remote server. AccountB doesn't have (and cannot have) permissions to do this. My shell gives me an Access Denied message. Fine.
So now, I need to have VBA return the name of AccountA, the account I have used to log into the computer. How do I do this?
I have seen quite a few examples on this site as well as others that will return the username running Excel (AccountB). However, I have not seen any examples that will pull AccountA information to pass to the Shell via RunAs to carry out my commands with the proper permissions. Below are some things I have tried, all returning AccountB (the account used via RunAs to launch Excel)
This application will be used by multiple people with permissions to run cmd shell on the remote server, so AccountA cannot be hardcoded and must be programmatically obtained.
' Access the GetUserNameA function in advapi32.dll and
' call the function GetUserName.
Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" _
(ByVal lpBuffer As String, nSize As Long) As Long
Sub XXXXXXXXXX()
'//
ThisWorkbook.Sheets("XXXXXXXXXX").Activate ' select the worksheet for use
cmdString = UCase(Trim(ThisWorkbook.ActiveSheet.Cells(4, 4).Value)) 'get XXXXXXXXXX
'MsgBox cmdString
'retval = Shell("cmd.exe /k " & cmdString, vbNormalNoFocus)
'cmdString = "MkDir \\XXXXXXXXXX\g$\Tempski" 'fails - no permission
'Set the Domain
txtdomain = "XXXXXXXXXX"
'Acquire the currently logged on user account
'txtuser = "XXXXXXXXXX"
' Dimension variables
Dim lpBuff As String * 255
Dim ret As Long
' Get the user name minus any trailing spaces found in the name.
ret = GetUserName(lpBuff, 255)
If ret > 0 Then
GetLogonName = Left(lpBuff, InStr(lpBuff, Chr(0)) - 1)
Else
GetLogonName = vbNullString
End If
MsgBox GetLogonName
Dim objNet As Object
On Error Resume Next
Set objNet = CreateObject("WScript.NetWork")
MsgBox "Network username is: " & objNet.UserName
Set objNet = Nothing
MsgBox "Environ username is: " & Environ("USERNAME")
MsgBox "Application username is: " & Application.UserName
MsgBox "Network username is: " & WindowsUserName
Dim strUser As String
strUser = ActiveDirectory.user()
MsgBox "Welcome back, " & strUser
CurrentWorkbenchUser = Environ("USERDOMAIN") & "\" & Environ("USERNAME")
MsgBox CurrentWorkbenchUser
Set WSHnet = CreateObject("WScript.Network")
UserName = WSHnet.UserName
UserDomain = WSHnet.UserDomain
Set objUser = GetObject("WinNT://" & UserDomain & "/" & UserName & ",user")
GetUserFullName = objUser.FullName
MsgBox GetFullUserName
'//try to pass user credentials to shell - service account cannot run XXXXXXXXXX
'//This Works when txtdomain and txtuser are passed properly (MUST GRAB THESE SOMEHOW)
'retval = Shell("runas /user:" & txtdomain & "\" & txtuser & " ""cmd.exe /k dir /s *.*""", vbNormalFocus)
End Sub
All the bits of code above return the account used to launch Excel — not what I need i.e. the account I used to log into the computer.
The basic approach is to execute the shell with a "run as" parameter.
have you taken a look at this
also look here
This is code ripped from the ms sight and is just here to make sure that the next guy or gal who comes by has a quick reference.
Sub RegisterFile(ByVal sFileName As String)
ShellExecute 0, "runas", "cmd", "/c regsvr32 /s " & """" & sFileName & """", "C:\", 0 'SW_HIDE =0
End Sub
As an aside if you only need the account for access to a SQL server, you should be able to just set the account within the connection string in your vba MACRO. I've done that for an Oracle DB in the past.

Running 7Z in a dos command from VBA Excel causes warning alert

I am currently using the following code to run a dos command as follows from VBA.
Set objShell = CreateObject("WScript.Shell")
dos_command="\\\10.xx.xx.xx\test\7z.exe a -r " etc etc etc
result = objShell.Run(dos_command, 0, True)
Set objShell =nothing
All runs well, the only problem is that I get an annoying Warning Windows Box advising a program is trying to run in my computer, press OK or Cancel
I must use "objshell" because I need VBA to wait until DOS command is completed.
is there a way to avoid the warning box from coming up from within VBA or adding some additional parameters to the DOS command ?
The 7z.exe file is running in a server (not local PC) so I assume that's the problem.
I cannot use or install 7z.exe in each machine.
Here are three options, presented in order from quickest/dirtiest to most robust:
Create a text file as part of command line and wait for its existence: modify your command line to something like this and run it using Shell (not your objShell):
dos_command = "\\\10.xx.xx.xx\test\7z.exe a -r " etc etc etc
dos_command = dos_command & " && echo > " & TempFileName
This will create a text file named TempFileName after your 7-zip code completes. You just need to make sure TempFileName does not exist before you run your shell command, then run the command and wait for the TempFileName file to exist.
Use OpenProcess and GetExitCodeProcess APIs: launch your command line using the OpenProcess API call which provides access to your new process (note that the Shell function returns the ProcessID of the launched process). Then use the ProcessID to sit in a loop and poll the process via GetExitCodeProcess. Relevant declarations:
Private Declare Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, _
lpExitCode As Long) As Long
Private Const STILL_ACTIVE = &H103
Private Const PROCESS_QUERY_INFORMATION = &H400
'---------------------------------------------------------------------------------------vv
' Procedure : ShellWait
' DateTime : 2/15/2008 10:59
' Author : Mike
' Purpose : Executes a shell command and waits for it to complete.
' Notes : Runs the shell as a batch file, allowing the user to pass a string with
' line breaks to execute a multi-line command.
'
' : Provides two means to break out of the loop.
' 1) Provide a timeout in seconds.
' The code breaks out once it reaches the timeout.
' 2) Provide a flag to tell the procedure to stop running.
' To use this option, you would need to pass the procedure a global flag
' that the user has the ability to change through the interface.
' Update (5/23/2008):
' - Uses a progressive sleep timer to allow fast processes to run quickly
' and long processes to get increasing clock cycles to work with.
' - Changed default window mode to hidden.
'---------------------------------------------------------------------------------------
'^^
Public Function ShellWait(DosCmd As String, _
Optional StartIn As String = "WINDOWS TEMP FOLDER", _
Optional WindowStyle As VbAppWinStyle = vbHide, _
Optional TimeOutSeconds As Long = -1, _
Optional ByRef StopWaiting As Boolean = False) 'vv
On Error GoTo Err_ShellWait
Dim hProcess As Long, RetVal As Long, StartTime As Long
Dim BatName As String, FileNum As Integer, SleepTime As Long
StartTime = Timer
BatName = TempFileName(StartIn, "bat")
FileNum = FreeFile()
Open BatName For Output As #FileNum
ChDrive Left(BatName, 1)
ChDir Left(BatName, InStrRev(BatName, "\"))
Print #FileNum, DosCmd
Close #FileNum
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, Shell(BatName, WindowStyle))
SleepTime = 10
Do
'Get the status of the process
GetExitCodeProcess hProcess, RetVal
DoEvents: Sleep SleepTime
If TimeOutSeconds <> -1 Then
If Timer - StartTime > TimeOutSeconds Then Exit Do
End If
If StopWaiting Then Exit Do
'Progressively increase the SleepTime by 10%
' This allows a quick process to finish quickly, while providing
' a long process with increasingly greater clock cycles to work with
SleepTime = SleepTime * 1.1
Loop While RetVal = STILL_ACTIVE
Kill BatName
Exit_ShellWait:
Exit Function
Err_ShellWait:
MsgBox Err.Description
Resume Exit_ShellWait
End Function
'---------------------------------------------------------------------------------------vv
' Procedure : TempFileName
' DateTime : 12/9/08
' Author : Mike
' Purpose : Returns an unused file name but does not create the file. Path can be
' passed with or without the trailing '\'.
' Requires : TempPath() function
'---------------------------------------------------------------------------------------
'^^
Function TempFileName(Optional ByVal Path As String = "WINDOWS TEMP FOLDER", _
Optional Ext As String = "txt", _
Optional Prefix As String = "temp") As String 'vv
Dim TempFName As String, i As Integer
If Path = "WINDOWS TEMP FOLDER" Then Path = TempPath
If Right(Path, 1) <> "\" Then Path = Path & "\"
If Not (Path Like "?:\*" Or Path Like "\\*") Then
Err.Raise 52 '"Bad file name or number."
ElseIf Dir(Path, vbDirectory) = "" Then
Err.Raise 76 '"Path not found."
End If
TempFName = Path & Prefix & "." & Ext
For i = 1 To 500
If Dir(TempFName) = "" Then
TempFileName = TempFName
GoTo Exit_TempFileName
End If
TempFName = Path & Prefix & "_" & Format(i, "000") & "." & Ext
Next i
TempFileName = ""
End Function
'---------------------------------------------------------------------------------------
' Procedure : TempPath
' Author : Mike
' Date : 8/12/2008
' Purpose : Returns something like:
' C:\DOCUME~1\BGRAND~1\LOCALS~1\Temp\
'---------------------------------------------------------------------------------------
'^^
Function TempPath() As String 'vv
Const TemporaryFolder = 2
Static TempFolderPath As String
Dim fs As Object
If Len(TempFolderPath) = 0 Then
Set fs = CreateObject("Scripting.FileSystemObject")
TempFolderPath = fs.GetSpecialFolder(TemporaryFolder) & "\"
End If
TempPath = TempFolderPath
End Function
Use CreateProcess and WaitForSingleObject APIs: refer to the "Super Shell" example at this help page for CreateProcess
Calling Microsoft® Windows® Script Host causes windows to display the message. Instead try this
Public Sub test()
Dim dos_command$, lRet&
dos_command = """\\xxx.xxx.xxx.xxx\xxx\xxx\7z.exe"" a test.zip ""\\xxx.xxx.xxx.xxx\xxx\xxx\*.log"" -r"
lRet = Shell(dos_command, vbMaximizedFocus)
MsgBox lRet
End Sub
UPDATE
You may do the following and use your code:
Open Start | Run and type gpedit.msc. Click OK
User Configuration >> Administrative Templates >> Windows Components >> Attachment Manager
Add 7z.exe to the Inclusion list for moderate risk file types setting.
Hpe this helps