Why is this call to InvokeVerb ("&Print") not working? - vba

I'm just trying to batch print a large amount of files from a folder. The folder contains multiple file types, and I just want to invoke the print method equivalent to Right-Click > Print.
It seems like I should be able to do this using the InvokeVerb method of Shell32.FolderItem object.
So, I can't figure out why, when I run the code below, nothing prints.
Any ideas?
( GetFolder is just a wrapper for the Shell32.BrowseForFolder function which returns the path to the folder selected. That function works without issue. For testing you can just replace with a path to a folder.)
Sub printFOO()
Dim shApp As Shell32.Shell
Dim srFSO As Scripting.FileSystemObject
Dim strPath As String
Dim shFIcol As Shell32.FolderItems
Dim shFIx As Shell32.FolderItem
Dim shFLDx As Shell32.Folder
Dim lngX As Long
Set shApp = New Shell32.Shell
Set srFSO = New Scripting.FileSystemObject
strPath = GetFolder("Choose a folder...")
Set shFLDx = shApp.NameSpace(strPath)
Set shFIcol = shFLDx.Items()
For Each shFIx In shFIcol
'For lngX = 0 To shFIx.Verbs.Count
'Debug.Print shFIx.Verbs.ITEM(lngX).Name
'Next
'msgbox("printing "&shFIx.name)
shFIx.InvokeVerb ("&Print")
DoEvents
Next
End Sub

Here's a program that does it using slightly different method. It also lists verbs available.
HelpMsg = vbcrlf & " ShVerb" & vbcrlf & vbcrlf & " David Candy 2014" & vbcrlf & vbcrlf & " Lists or runs an explorer verb (right click menu) on a file or folder" & vbcrlf & vbcrlf & " ShVerb <filename> [verb]" & vbcrlf & vbcrlf & " Used without a verb it lists the verbs available for the file or folder" & vbcrlf & vbcrlf
HelpMsg = HelpMsg & " The program lists most verbs but only ones above the first separator" & vbcrlf & " of the menu work when used this way" & vbcrlf & vbcrlf
HelpMsg = HelpMsg & " The Properties verb can be used. However the program has to keep running" & vbcrlf & " to hold the properties dialog open. It keeps running by displaying" & vbcrlf & " a message box."
Set objShell = CreateObject("Shell.Application")
Set Ag = WScript.Arguments
set WshShell = WScript.CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
If Ag.count = 0 then
wscript.echo " ShVerb - No file specified"
wscript.echo HelpMsg
wscript.quit
Else If Ag.count = 1 then
If LCase(Replace(Ag(0),"-", "/")) = "/h" or Replace(Ag(0),"-", "/") = "/?" then
wscript.echo HelpMsg
wscript.quit
End If
ElseIf Ag.count > 2 then
wscript.echo vbcrlf & " ShVerb - To many parameters" & vbcrlf & " Use quotes around filenames and verbs containing spaces" & vbcrlf
wscript.echo HelpMsg
wscript.quit
End If
If fso.DriveExists(Ag(0)) = True then
Set objFolder = objShell.Namespace(fso.GetFileName(Ag(0)))
' Set objFolderItem = objFolder.ParseName(fso.GetFileName(Ag(0)))
Set objFolderItem = objFolder.self
msgbox ag(0)
ElseIf fso.FolderExists(Ag(0)) = True then
Set objFolder = objShell.Namespace(fso.GetParentFolderName(Ag(0)))
Set objFolderItem = objFolder.ParseName(fso.GetFileName(Ag(0)))
ElseIf fso.fileExists(Ag(0)) = True then
Set objFolder = objShell.Namespace(fso.GetParentFolderName(Ag(0)))
Set objFolderItem = objFolder.ParseName(fso.GetFileName(Ag(0)))
Else
wscript.echo " ShVerb - " & Ag(0) & " not found"
wscript.echo HelpMsg
wscript.quit
End If
Set objVerbs = objFolderItem.Verbs
'If only one argument list verbs for that item
If Ag.count = 1 then
For Each cmd in objFolderItem.Verbs
If len(cmd) <> 0 then CmdList = CmdList & vbcrlf & replace(cmd.name, "&", "")
Next
wscript.echo mid(CmdList, 2)
'If two arguments do verbs for that item
ElseIf Ag.count = 2 then
For Each cmd in objFolderItem.Verbs
If lcase(replace(cmd, "&", "")) = LCase(Ag(1)) then
wscript.echo Cmd.doit
Exit For
End If
Next
'Properties is special cased. Script has to stay running for Properties dialog to show.
If Lcase(Ag(1)) = "properties" then
WSHShell.AppActivate(ObjFolderItem.Name & " Properties")
msgbox "This message box has to stay open to keep the " & ObjFolderItem.Name & " Properties dialog open."
End If
End If
End If

You dont need the FSO for folder browse dialogue. Try shApp.BrowseForFolder(0, "Select Folder to print from", 0, 0). By this method you get directly the shell folder object. Also you may need to check each folder-item if is file or folder.
Sub printFgOO()
Dim shApp As Shell32.Shell
Dim shFIcol As Shell32.FolderItems
Dim shFIx As Shell32.FolderItem
Dim shFLDx As Shell32.Folder
Dim lngX As Long
Set shApp = New Shell32.Shell
Set shFLDx = shApp.BrowseForFolder(0, "Select Folder to print from", 0, 0)
Set shFIcol = shFLDx.Items()
For Each shFIx In shFIcol
If Not shFIx.IsFolder Then ' Print only if is file
shFIx.InvokeVerb ("&Print")
DoEvents
End If
Next
End Sub
OR try function ShellExecute as described here!

Okay, so I still don't have an answer as to WHY the InvokeVerb method was not working to print, but I do have a way to print files now using the ShellExecute function suggested by #Radek.
Figured I would share my working code here. Feel free to suggest improvements ;)
Option Explicit
Public Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteW" (ByVal hWnd As Long, _
ByVal lpOperation As LongPtr, _
ByVal lpFile As LongPtr, _
ByVal lpParameters As LongPtr, _
ByVal lpDirectory As LongPtr, _
ByVal nShowCmd As Long) As Long
Public Const SW_HIDE As Long = 0
'Hides the window and activates another window.
Public Const SW_MAXIMIZE As Long = 3
'Maximizes the specified window.
Public Const SW_MINIMIZE As Long = 6
'Minimizes the specified window and activates the next top-level window in the z-order.
Public Const SW_RESTORE As Long = 9
'Activates and displays the window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when restoring a minimized window.
Public Const SW_SHOW As Long = 5
'Activates the window and displays it in its current size and position.
Public Const SW_SHOWDEFAULT As Long = 10
'Sets the show state based on the SW_ flag specified in the STARTUPINFO structure passed to the CreateProcess function by the program that started the application. An application should call ShowWindow with this flag to set the initial show state of its main window.
Public Const SW_SHOWMAXIMIZED As Long = 3
'Activates the window and displays it as a maximized window.
Public Const SW_SHOWMINIMIZED As Long = 2
'Activates the window and displays it as a minimized window.
Public Const SW_SHOWMINNOACTIVE As Long = 7
'Displays the window as a minimized window. The active window remains active.
Public Const SW_SHOWNA As Long = 8
'Displays the window in its current state. The active window remains active.
Public Const SW_SHOWNOACTIVATE As Long = 4
'Displays a window in its most recent size and position. The active window remains active.
Public Const SW_SHOWNORMAL As Long = 1
'Activates and displays a window. If the window is minimized or maximized, Windows restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
Public Enum shexActions
shexEDIT
shexEXPLORE
shexFIND
shexOPEN
shexPRINT
End Enum
Private Function getShellAction(ByRef enACTION As shexActions) As String
Select Case enACTION
Case shexActions.shexEDIT
getShellAction = "EDIT"
Case shexActions.shexEXPLORE
getShellAction = "EXPLORE"
Case shexActions.shexFIND
getShellAction = "FIND"
Case shexActions.shexOPEN
getShellAction = "OPEN"
Case shexActions.shexprint
getShellAction = "PRINT"
End Select
End Function
Public Function ShellEx(ByRef strFILE As String, _
Optional ByRef lngWINDOWHANDLE As Long = 0, _
Optional ByRef shexACTION As shexActions = (-1), _
Optional ByRef strPARAMETERS As String, _
Optional ByRef strDIRECTORY As String, _
Optional ByRef lngSHOWCOMMAND As Long = 0) As Long
Dim lngReturnCheck As Long
lngReturnCheck = (-1)
lngReturnCheck = ShellExecute(hWnd:=lngWINDOWHANDLE, lpOperation:=StrPtr(getShellAction(shexACTION)), lpFile:=StrPtr(strFILE), lpParameters:=StrPtr(strPARAMETERS), lpDirectory:=StrPtr(strDIRECTORY), nShowCmd:=lngSHOWCOMMAND)
While lngReturnCheck = (-1)
DoEvents
Wend
ShellEx = lngReturnCheck
End Function
Sub printBAR()
Dim shFIcol As Shell32.FolderItems
Dim shFIx As Shell32.FolderItem
Dim shFLDx As Shell32.Folder
Dim lngX As Long
Set shFLDx = GetFolder("Choose a folder...", True)
Set shFIcol = shFLDx.Items()
For Each shFIx In shFIcol
lngX = ShellEx(shFIx.Path, , shexPRINT)
Debug.Print lngX
Next
End Sub

If you are open to using powershell to solve this:
#Save this as Print-Files.ps1
[CmdletBinding()]
param(
[Property(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string]$Path)
foreach($file in (Get-ChildItem $path ))
{
Start-Process –FilePath $file.FullName –Verb Print
}

Related

On Form_Open() check links to backend data

I'm using a template database that I found somewhere on the internet quite a while ago. I really wish I remember where I found it so I could at least give the author credit for the routines and back-up ideas, but I haven't had any luck thus far.
I am having an issue with the back-end check on loading the database. Here is the code I'm using:
Private Sub Form_Open(Cancel As Integer)
On Error GoTo Err_Handler
Const conFILENOTFOUND As Integer = 3024
Const conPATHNOTFOUND As Integer = 3044
Dim dbs As DAO.Database, rst As DAO.Recordset, tdf As DAO.TableDef
Dim strTable As String, strConnect As String
Set dbs = CurrentDb
' mimimize database window/navigation pane
' DoCmd.SelectObject acForm, Me.Name, True
' DoCmd.Minimize
' test validity of links to back end and open
' form to refersh links if not valid
CheckLinks:
For Each tdf In dbs.TableDefs
If Len(tdf.Connect) > 0 Then
If tdf.Connect <> strConnect Then
strTable = tdf.Name
Set rst = dbs.OpenRecordset(strTable)
strConnect = tdf.Connect
End If
End If
Next tdf
Exit_Here:
Set rst = Nothing
Set tdf = Nothing
Set dbs = Nothing
Exit Sub
Err_Handler:
If Err.Number = conFILENOTFOUND Or Err.Number = conPATHNOTFOUND Then
DoCmd.OpenForm "frmUpdate_Links", _
WindowMode:=acDialog, _
OpenArgs:="ForceQuit"
Resume CheckLinks
Else
MsgBox Err.Description & " (" & Err.Number & ")"
Resume Exit_Here
End If
End Sub
The problem lies in the fact that the form isn't firing back at me saying the back-end is wrong (well, to be honest it IS doing this...) and opening frmUpdate_Links to update the backend links. I'm thinking the conFILENOTFOUND and/or conPATHNOTFOUND error checks are incorrect. I'm currently working with a database that doesn't have any entries in the two tables it uses to check whether the back-end exists or not. Those tables are BackEndLocation and FileLocations. It's supposed to open frmUpdate_Links when there is no entry in these two tables. Instead I get the typical error that occurs when a database cannot find it's back-end.
There are two modules associated with this routine. Here is their code:
First one is BrowseForFileClass which is a Class Module -
Option Compare Database
Option Explicit
' There are default values for the dialog box title and the list of file types
' in the 'file filter' section of the dialog box. The calling VBA code can
' use the following Properties and Methods of this class.
'
' Properties:
' DialogTitle -- the text that is displayed as the title of the
' dialog box. The default is "Browse For a File".
' AdditionalTypes -- one or more additional file types to be added as
' one item in the dialog box's file filter list,
' formatted like this sample:
' "My Files (*.mf1;*.mf2) | *.mf1;*.mf2 | Your Files (*.yf1;*.yf2) *.yf1;*.yf2"
' The following file types are in the built-in list:
' "All Files (*.*)"
' "Text Files (*.txt;*.prn;*.csv)"
' "Word Documents (*.doc)"
' "Word Templates (*.dot)"
' "Rich Text Files (*.rtf)"
' "Excel Files (*.xls)"
' "Databases (*.mdb)"
' "HTML Documents (*.html;*.htm)"
' DefaultType -- the item in the dialog's file filter list that will be
' active when the dialog box is activated. If the
' AdditionalTypes property is not used, the default
' is "All files (*.*)". If the AdditionalTypes property
' is used, this property cannot be used and the file type
' specified in the AdditionalTypes property will be active
' when the dialog box is activated. To set this property,
' specify a string that will match with the desired type,
' such as "*.doc" or "HTML".
' InitialFile -- the file name that is to be displayed in the File Name
' field in the dialog box when it is activated. The
' default is to leave the File Name field blank.
' InitialDir -- the directory/folder which should be active when the
' dialog box is activated. The default is the current
' directory.
'
' Methods:
' GetFileSpec() -- this function activates the dialog box and then returns
' the full path and filename of the file that the User
' has selected. If the User clicks Cancel, a zero
' length string is returned.
'
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As Long
nMaxCustrFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
Flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustrData As Long
lpfnHook As Long
lpTemplateName As Long
End Type
Private Declare Function GetOpenFileName Lib "comdlg32.dll" Alias _
"GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Boolean
Private strDialogTitle As String
Private intDefaultType As Integer
Private strNewTypes As String
Private strInitialFile As String
Private strInitialDir As String
Private strFilter As String
Private strFltrLst As String
Private strFltrCnt As String
' This 'Method' routine displays the Open dialog box for the user to
' locate the desired file. Returns the full path to the file.
'
Public Function GetFileSpec()
Dim of As OPENFILENAME
Dim intRet As Integer
'set up the file filter and the default type option
If strNewTypes <> "" Then
of.lpstrFilter = strNewTypes & strFilter
of.nFilterIndex = 1
Else
of.lpstrFilter = strFilter
If intDefaultType <> 0 Then
of.nFilterIndex = intDefaultType
Else
of.nFilterIndex = 1
End If
End If
'define some other dialog options
of.lpstrTitle = strDialogTitle
of.lpstrInitialDir = strInitialDir
of.lpstrFile = strInitialFile & String(512 - Len(strInitialFile), 0)
of.nMaxFile = 511
' Initialize other parts of the structure
of.hwndOwner = Application.hWndAccessApp
of.hInstance = 0
of.lpstrCustomFilter = 0
of.nMaxCustrFilter = 0
of.lpfnHook = 0
of.lpTemplateName = 0
of.lCustrData = 0
of.lpstrFileTitle = String(512, 0)
of.nMaxFileTitle = 511
of.lpstrDefExt = vbNullChar
of.Flags = 0
of.lStructSize = Len(of)
'call the Open dialog routine
intRet = GetOpenFileName(of)
If intRet Then
GetFileSpec = Left(of.lpstrFile, InStr(of.lpstrFile, vbNullChar) - 1)
Else
GetFileSpec = ""
End If
End Function 'End of GetFileSpec
Public Property Let DialogTitle(strTitle As String)
'store the title for the dialog box
strDialogTitle = strTitle
End Property
Public Property Let AdditionalTypes(strAddTypes As String)
Dim Posn As Integer
Dim i As Integer
'don't accept additional types if a default type has been specified
If intDefaultType <> 0 Then
MsgBox "You cannot add to the file type filter if a default type is " & _
"being specified in the DefaultType property. When the " & _
"AdditionalTypes property is used, that item " & _
"is used as the default in the file type filter.", vbCritical, _
"Browse For File Dialog"
Exit Property
End If
'check for the "|" delimiter
Posn = InStr(strAddTypes, "|")
'save the new parameter or report an error
If Posn = 0 Then
MsgBox "The AdditionalTypes property string does not contain at least " & _
"one " & Chr$(34) & "|" & Chr$(34) & " character. " & _
"You must specify an AdditionalTypes property in the same " & _
"format that is shown in the " & _
"following example: " & vbCrLf & vbCrLf & Chr$(34) & _
"My Files (*.mf1;*.mf2) | *.mf1;*.mf2 | Your Files (*.yf1;*.yf2) *.yf1;*.yf2" _
& Chr$(34), vbCritical, "Browse For File Dialog"
strNewTypes = ""
Exit Property
Else
Do While True
If InStr(1, strAddTypes, "|") Then
strNewTypes = strNewTypes & Left$(strAddTypes, _
InStr(1, strAddTypes, "|") - 1) & vbNullChar
strAddTypes = Mid$(strAddTypes, InStr(1, strAddTypes, "|") + 1)
Else
strNewTypes = strNewTypes & vbNullChar
Exit Do
End If
Loop
End If
End Property 'End of AdditionalTypes
Public Property Let DefaultType(strType As String)
Dim Posn As Integer
Posn = InStr(strFltrLst, strType)
'don't accept a default if new types are being specified
If strNewTypes <> "" Then
MsgBox "You cannot set the DefaultType property if you are using the " & _
"AdditionalTypes property to expand the file types filter. " & _
"In that case the type specified in the AdditionalTypes property " & _
"will be the default type.", vbCritical, "Browse For File Dialog"
Exit Property
'make sure the selected default actually exists
ElseIf Posn = 0 Then
MsgBox "The file type you specified in the DefaultType " & _
"property is not in the built-in " & _
"list of file types. You must either specify one of the " & _
"built-in file types or use the AdditionalTypes property " & _
"to specify a complete entry similar to the " & _
"following example: " & vbCrLf & vbCrLf & Chr$(34) & _
"My Files (*.mf) | *.mf" & Chr$(34), vbCritical, _
"Browse For File Dialog"
Exit Property
Else
'set up the selected default
intDefaultType = Trim$(Mid$(strFltrCnt, Posn, 3))
End If
End Property
Public Property Let InitialFile(strIFile As String)
strInitialFile = strIFile
End Property
Public Property Let InitialDir(strIDir As String)
strInitialDir = strIDir
End Property
' This routine initializes the string constants that are used by this class
'
Private Sub Class_Initialize()
'define some initial conditions
strDialogTitle = "Browse For a File"
strInitialDir = ""
strInitialFile = ""
strNewTypes = ""
'define the filter string and the look-up strings
strFilter = "All Files (*.*)" & vbNullChar & "*.*" & vbNullChar & _
"Text Files (*.txt;*.prn;*.csv)" & vbNullChar & "*.txt;*.prn;*.csv" & vbNullChar & _
"Word Documents (*.doc)" & vbNullChar & "*.doc" & vbNullChar & _
"Word Templates (*.dot)" & vbNullChar & "*.dot" & vbNullChar & _
"Rich Text Files (*.rtf)" & vbNullChar & "*.rtf" & vbNullChar & _
"Excel Files (*.xls)" & vbNullChar & "*.xls" & vbNullChar & _
"Databases (*.mdb;*.accdb)" & vbNullChar & "*.mdb;*.accdb" & vbNullChar & _
"Personal Document Format (*.pdf)" & vbNullChar & "*.pdf" & vbNullChar & _
"HTML Documents (*.html;*.htm)" & vbNullChar & "*.html;*.htm" & vbNullChar
strFltrLst = "*.* *.txt *.prn *.csv *.doc *.dot *.rtf *.xls *.mdb *.accdb *.pdf *.html *.htm"
strFltrCnt = " 1 2 2 2 3 4 5 6 7 7 8 9 9"
End Sub
And here is the second module, modBackup -
Option Compare Database
Option Explicit
Declare Function CopyFile& Lib "kernel32" Alias "CopyFileA" (ByVal _
lpExistingFilename As String, ByVal lbNewFileName As String, ByVal _
bFailIfExists As Long)
Public AllowClose As Boolean
Public Sub MakeFileCopy(strExistingFile As String, _
strNewfile As String, _
blnDoNotOverWrite As Boolean, _
Optional blnShowMessage As Boolean = False)
Dim strMessage As String
strExistingFile = strExistingFile
strNewfile = strNewfile
If CopyFile(strExistingFile, strNewfile, blnDoNotOverWrite) = 1 Then
strMessage = "File successfully copied."
Else
strMessage = "File copy failed."
End If
If blnShowMessage Then
MsgBox strMessage, vbInformation, "Copy File"
End If
End Sub
Public Function BackUp(strBackEnd As String, strBackUp As String) As Boolean
Const FILEINUSE = 3356
Dim dbs As DAO.Database
Dim strMessage As String
Dim strBackUpTemp As String
' if back up file exists get user confirmation
' to delete it
If Dir(strBackUp) <> "" Then
strMessage = "Delete existing file " & strBackUp & "?"
If MsgBox(strMessage, vbQuestion + vbYesNo, "Confirm") = vbNo Then
strMessage = "Back up aborted."
MsgBox strMessage, vbInformation, "Back up"
Exit Function
Else
' make temporary copy of backend file and then delete it
strBackUpTemp = Left(strBackUp, InStr(strBackUp, ".")) & "bak"
MakeFileCopy strBackUp, strBackUpTemp, False
Kill strBackUp
End If
End If
On Error Resume Next
' attempt to open backend exclusively
Set dbs = OpenDatabase(Name:=strBackEnd, Options:=True)
Select Case Err.Number
Case 0
' no error so proceed
dbs.Close
Application.CompactRepair strBackEnd, strBackUp
If Err.Number = FILEINUSE Then
' file in use by current user
strMessage = "The file " & strBackEnd & _
" is currently unavailable. " & _
" You may have a table in it open."
MsgBox strMessage
' rename temporary copy of back up file
' if exists, back to original
If Dir(strBackUpTemp) <> "" Then
MakeFileCopy strBackUpTemp, strBackUp, False
Kill strBackUpTemp
End If
Exit Function
Else
On Error GoTo 0
' ensure back up file created
If Dir(strBackUp) = Mid(strBackUp, InStrRev(strBackUp, "\") + 1) Then
strMessage = "Back up successfully carried out."
BackUp = True
' delete temporary copy of back up file if exists
On Error Resume Next
Kill strBackUpTemp
On Error GoTo 0
Else
strMessage = "Back up failed."
' rename temporary copy of back up file
' if exists, back to original
If Dir(strBackUpTemp) <> "" Then
MakeFileCopy strBackUpTemp, strBackUp, False
Kill strBackUpTemp
End If
End If
MsgBox strMessage, vbInformation, "Back up"
End If
Case FILEINUSE
' file in use - inform user
strMessage = "The file " & strBackEnd & _
" is currently unavailable. " & _
" It may be in use by another user."
MsgBox strMessage
' rename temporary copy of back up file,
' if exists, back to original
If Dir(strBackUpTemp) <> "" Then
MakeFileCopy strBackUpTemp, strBackUp, False
Kill strBackUpTemp
End If
Case Else
' unknown error - inform user
MsgBox Err.Description, vbExclamation, "Error"
' rename temporary copy of back up file
' if exists, back to original
If Dir(strBackUpTemp) <> "" Then
MakeFileCopy strBackUpTemp, strBackUp, False
Kill strBackUpTemp
End If
End Select
End Function
Public Function GetBackEndPath() As Variant
GetBackEndPath = DLookup("BackEndPath", "FileLocations")
End Function
Public Function GetBackUpPath() As Variant
GetBackUpPath = DLookup("BackUpPath", "FileLocations")
End Function
I am 100% uncertain which errors the CheckLinks sub-routine is supposed to be looking for. I tried to find some information regarding the different errors, such as 3024 and 3044 but they didn't provide me any useful information as to how exactly these error codes associate with this routine.
The wacky part is the original "template" database works perfectly in all aspects. I copy/pasted over all the modules, routines, forms, etc. and made them my "own" to match up with the host database styles and themes, and now they don't work. What the heck am I doing wrong?
Thanks!
So I figured out what the problem was. The initial form must not be bound to any data. It needs to not rely on the backend in order to load "to the point" of executing the subroutines which check for the proper back-end files.

Wait for sending to printer (shellexecute) before continue

I want to print all emails and attachments of an Outlook folder. I want to print excel, word and pfd files.
This works but not in the right order. Emails en printed attachments get mixed-up. So i want to synchronize the printing. The process has to wait until the print job has been send. The problem is probably that ShellExecute command works asynchronically from the VBA.
So how can I let the VBA wait until the ShellExecute has finished. I've read on the MSDN that I have to use the CreateProcess but I don't know how to use a print command on this. It only runs an application.
I also tried to use the Sleep method in VBA to give the printing some time but it doesn't seems to be the right solution or work very good. Please has anyone an advice?
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
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr)
Sub SaveBijlageArgumenten()
SaveEmailAttachmentsToFolder "Postvak IN", "Account...", "xlsx", "xls", "pdf", "doc", "docx", "C:\....."
End Sub
Sub SaveEmailAttachmentsToFolder(OutlookInbox As String, OutlookAccount As String, _
ExtString As String, ExtString2 As String, ExtString6 As String, ExtString3 As String, ExtString4 As String, _
ExtString5 As String, DestFolder As String)
Dim ns As NameSpace
Dim Inbox As MAPIFolder
Dim SubFolder As MAPIFolder
Dim Item As Object
Dim Atmt As Attachment
Dim FileName As String
Dim MyDocPath As String
Dim I As Integer
Dim wsh As Object
Dim fs As Object
Dim xlApp As Object
Dim myBook As Object
' Create Excel Application
Set xlApp = CreateObject("Excel.Application")
xlApp.Visible = False 'Visible is False by default, so this isn't necessary
On Error GoTo ThisMacro_err
Set ns = GetNamespace("MAPI")
Set Inbox = ns.Folders(OutlookAccount)
Set SubFolder = Inbox.Folders(OutlookInbox)
I = 0
' Check subfolder for messages and exit of none found
If SubFolder.Items.Count = 0 Then
MsgBox "There are no messages in this folder : " & OutlookFolderInInbox, _
vbInformation, "Nothing Found"
Set SubFolder = Nothing
Set Inbox = Nothing
Set ns = Nothing
Exit Sub
End If
'Create DestFolder if DestFolder = ""
Set fs = CreateObject("Scripting.FileSystemObject")
If DestFolder = "" Then
Set wsh = CreateObject("WScript.Shell")
Set fs = CreateObject("Scripting.FileSystemObject")
MyDocPath = wsh.SpecialFolders.Item("mydocuments")
DestFolder = MyDocPath & "\" & Format(Now, "dd-mmm-yyyy hh-mm-ss")
If Not fs.FolderExists(DestFolder) Then
fs.CreateFolder DestFolder
End If
Else
DestFolder = DestFolder & "\" & Format(Now, "dd-mmm-yyyy hh-mm-ss")
If Not fs.FolderExists(DestFolder) Then
fs.CreateFolder DestFolder
End If
End If
If Right(DestFolder, 1) <> "\" Then
DestFolder = DestFolder & "\"
End If
'On Error Resume Next
' Check each message for attachments and extensions
For Each Item In SubFolder.Items
Item.PrintOut Background:=False
Item.UnRead = False
Sleep 500
For Each Atmt In Item.Attachments
If LCase(Right(Atmt.FileName, Len(ExtString))) = LCase(ExtString) Or _
LCase(Right(Atmt.FileName, Len(ExtString2))) = LCase(ExtString2) Then
FileName = DestFolder & Item.SenderName & " " & Atmt.FileName
Atmt.SaveAsFile FileName
Set myBook = xlApp.Workbooks.Open(FileName, UpdateLinks:=0)
myBook.PrintOut Background:=False
myBook.Close SaveChanges:=False
I = I + 1
ElseIf LCase(Right(Atmt.FileName, Len(ExtString3))) = LCase(ExtString3) Or LCase(Right(Atmt.FileName, Len(ExtString4))) = LCase(ExtString4) _
Or LCase(Right(Atmt.FileName, Len(ExtString5))) = LCase(ExtString5) Then
FileName = DestFolder & Item.SenderName & " " & Atmt.FileName
Atmt.SaveAsFile FileName
ShellExecute 0, "print", FileName, vbNullString, vbNullString, 0
Sleep 3000
I = I + 1
End If
Next Atmt
Next Item
On Error GoTo ThisMacro_err
' Show this message when Finished
If I > 0 Then
MsgBox "De bestanden in de bijlage zijn opgeslagen op onderstaande locatie: " _
& DestFolder, vbInformation, "Klaar!"
Else
MsgBox "Er bevonden zich geen bijlagen bij de emails", vbInformation, "Klaar!"
End If
' Clear memory
ThisMacro_exit:
Set SubFolder = Nothing
Set Inbox = Nothing
Set ns = Nothing
Set fs = Nothing
Set wsh = Nothing
Set xlApp = Nothing
Set myBook = Nothing
Set AcroExchApp = Nothing
Set AcroExchAVDoc = Nothing
Exit Sub
' Error information
ThisMacro_err:
MsgBox "An unexpected error has occurred." _
& vbCrLf & "Please note and report the following information." _
& vbCrLf & "Macro Name: SaveEmailAttachmentsToFolder" _
& vbCrLf & "Error Number: " & Err.Number _
& vbCrLf & "Error Description: " & Err.Description _
, vbCritical, "Error!"
Resume ThisMacro_exit
End Sub
The WshShell object provides the Run method which has the bWaitOnReturn optional parameter. It indicates whether the script should wait for the program to finish executing before continuing to the next statement in your script. If set to true, script execution halts until the program finishes, and Run returns any error code returned by the program. If set to false (the default), the Run method returns immediately after starting the program, automatically returning 0 (not to be interpreted as an error code).

Access VBA to delete a file to the recycle bin?

Using the following code delete's my file, but it doesn't go to the recycle bin - does code exist that will send it to the recycle bin? Should I use ".Move" ?
If MsgBox("DELETE:" & Chr(10) & Forms("frmtbl").f_FullPath & Me.f_FileName & " ?", vbYesNo) = vbYes Then
'Kill Forms("frmtbl").f_FullPath & Me.f_FileName
Dim objFSO As Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFSO.DeleteFile (Forms("frmtbl").f_FullPath & Me.f_FileName)
DoCmd.Close acForm, Me.Name
Else
MsgBox "FILE NOT DELETED:" & Chr(10) & Forms("frmtbl").f_FullPath & Me.f_FileName & ".", vbInformation,
End If
.MoveFile to the recycle bin requires permissions I don't have.
An integrated VBA-method seems not to exist. API call is needed.
Following code is copied from reddit. (solution by "Crushnaut")
Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Windows API functions, constants,and types.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Declare PtrSafe Function SHFileOperation Lib "shell32.dll" Alias _
"SHFileOperationA" (lpFileOp As SHFILEOPSTRUCT) As LongPtr
Private Const FO_DELETE = &H3
Private Const FOF_ALLOWUNDO = &H40
Private Const FOF_NOCONFIRMATION = &H10
Private Type SHFILEOPSTRUCT
hwnd As LongPtr
wFunc As LongPtr
pFrom As String
pTo As String
fFlags As Integer
fAnyOperationsAborted As Boolean
hNameMappings As LongPtr
lpszProgressTitle As String
End Type
Public Function Recycle(FileSpec As String, Optional ErrText As String) As Boolean
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Recycle
' This function sends FileSpec to the Recycle Bin. There
' are no restriction on what can be recycled. FileSpec
' must be a fully qualified folder or file name on the
' local machine.
' The function returns True if successful or False if
' an error occurs. If an error occurs, the reason for the
' error is placed in the ErrText varaible.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim SHFileOp As SHFILEOPSTRUCT
Dim Res As LongPtr
Dim sFileSpec As String
ErrText = vbNullString
sFileSpec = FileSpec
If InStr(1, FileSpec, ":", vbBinaryCompare) = 0 Then
''''''''''''''''''''''''''''''''''''''
' Not a fully qualified name. Get out.
''''''''''''''''''''''''''''''''''''''
ErrText = "'" & FileSpec & "' is not a fully qualified name on the local machine"
Recycle = False
Exit Function
End If
If Dir(FileSpec, vbDirectory) = vbNullString Then
ErrText = "'" & FileSpec & "' does not exist"
Recycle = False
Exit Function
End If
''''''''''''''''''''''''''''''''''''
' Remove trailing '\' if required.
''''''''''''''''''''''''''''''''''''
If Right(sFileSpec, 1) = "\" Then
sFileSpec = Left(sFileSpec, Len(sFileSpec) - 1)
End If
With SHFileOp
.wFunc = FO_DELETE
.pFrom = sFileSpec
.fFlags = FOF_ALLOWUNDO
'''''''''''''''''''''''''''''''''
' If you want to supress the
' "Are you sure?" message, use
' the following:
'''''''''''''''''''''''''''''''
.fFlags = FOF_ALLOWUNDO + FOF_NOCONFIRMATION
End With
Res = SHFileOperation(SHFileOp)
If Res = 0 Then
Recycle = True
Else
Recycle = False
End If
End Function

How can I automate the addition from WorkbookConnections with VBA?

I want to connect csv files with excels power pivot with a VBA code by using WorkbookConnection.AddfromFile
My question:
I want to connect numerous csv file. To do so I have to click for hours through the Text Import Wizard. I haven't found out yet how to automatize this! I imagine to do it in a similar way like I did it with the FileDialog in the upper part of my code. Below the part of my code where I want to implement it.
For LoopCounter = 1 To fd.SelectedItems.count
ActiveWorkbook.Connections.AddFromFile _
fd.SelectedItems(LoopCounter), True, False
Next LoopCounter
Below the code I have already written. With this code I have to click through the TextImportWizard manually.
Sub csv()
Dim fd As FileDialog
Dim ActionClicked As Boolean
Dim LoopCounter As Long
Set fd = Application.FileDialog(msoFileDialogFilePicker)
fd.InitialFileName = "C:\temp"
fd.AllowMultiSelect = True
fd.Title = "Open your data"
fd.ButtonName = "GO"
ActionClicked = fd.Show
If ActionClicked Then
For LoopCounter = 1 To fd.SelectedItems.count
ActiveWorkbook.Connections.AddFromFile _
fd.SelectedItems(LoopCounter), True, False
Next LoopCounter
Else
MsgBox "You didn't choose anything"
Exit Sub
End If
End Sub
The faster way to import CSV or text files is with the following
Dim InputStringCSV As String
Dim CSVFile As Variant
Dim ArrayStringCSV() As String
CSVFile = Application.GetOpenFilename("CSV Files,*.CSV", Title:="MyData")
If CSVFile = False Then "No input!", vbCritical: End
Open CSVFile For Input As #1
Do Until EOF(1)
Line Input #1, InputStringCSV
ArrayStringCSV = Split(InputStringCSV, Chr(10))
For CounterArray = LBound(ArrayStringCSV) To UBound(ArrayStringCSV)
'Defaults: Row 1 is the beginnning for the sheet
Sheets(Sheet_CSV).Cells(1 + CounterArray, 1).NumberFormat = "#"
Sheets(Sheet_CSV).Cells(1 + CounterArray, 1) = ArrayStringCSV(CounterArray)
Next CounterArray
Loop
Close #1
If you want to automate it for all the CSV files in a folder, I'd suggest that you loop through archives -looking for .csv ones-, here's a sample on how to get you started:
Set oFSO = CreateObject("Scripting.FileSystemObject")
oStartFolder = "C:/Documents"
Set oFolder = oFSO.GetFolder(oStartFolder)
oFSO.GetFolder (oFolder)
For Each FileItem In oFolder.Files
if Instr(FileItem,".csv") Then Call ImportCSV(FileName) 'you would change the above code to don't ask folder and set the argument so each time you call it would be the file csv in the folder
Next FileItem
There are lots of ways to import text files. See the link below.
http://www.rondebruin.nl/win/s3/win022.htm
This AddIn will do the work for you.
http://www.rondebruin.nl/win/addins/rdbmerge.htm
Also, you can merge all your text files in a folder into one worksheet.
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, _
lpExitCode As Long) As Long
#Else
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
#End If
Public Const PROCESS_QUERY_INFORMATION = &H400
Public Const STILL_ACTIVE = &H103
Public Sub ShellAndWait(ByVal PathName As String, Optional WindowState)
Dim hProg As Long
Dim hProcess As Long, ExitCode As Long
'fill in the missing parameter and execute the program
If IsMissing(WindowState) Then WindowState = 1
hProg = Shell(PathName, WindowState)
'hProg is a "process ID under Win32. To get the process handle:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, hProg)
Do
'populate Exitcode variable
GetExitCodeProcess hProcess, ExitCode
DoEvents
Loop While ExitCode = STILL_ACTIVE
End Sub
Sub Merge_CSV_Files()
Dim BatFileName As String
Dim TXTFileName As String
Dim XLSFileName As String
Dim FileExtStr As String
Dim FileFormatNum As Long
Dim DefPath As String
Dim Wb As Workbook
Dim oApp As Object
Dim oFolder
Dim foldername
'Create two temporary file names
BatFileName = Environ("Temp") & _
"\CollectCSVData" & Format(Now, "dd-mm-yy-h-mm-ss") & ".bat"
TXTFileName = Environ("Temp") & _
"\AllCSV" & Format(Now, "dd-mm-yy-h-mm-ss") & ".txt"
'Folder where you want to save the Excel file
DefPath = Application.DefaultFilePath
If Right(DefPath, 1) <> "\" Then
DefPath = DefPath & "\"
End If
'Set the extension and file format
If Val(Application.Version) < 12 Then
'You use Excel 97-2003
FileExtStr = ".xls": FileFormatNum = -4143
Else
'You use Excel 2007 or higher
FileExtStr = ".xlsx": FileFormatNum = 51
'If you want to save as xls(97-2003 format) in 2007 use
'FileExtStr = ".xls": FileFormatNum = 56
End If
'Name of the Excel file with a date/time stamp
XLSFileName = DefPath & "MasterCSV " & _
Format(Now, "dd-mmm-yyyy h-mm-ss") & FileExtStr
'Browse to the folder with CSV files
Set oApp = CreateObject("Shell.Application")
Set oFolder = oApp.BrowseForFolder(0, "Select folder with CSV files", 512)
If Not oFolder Is Nothing Then
foldername = oFolder.Self.Path
If Right(foldername, 1) <> "\" Then
foldername = foldername & "\"
End If
'Create the bat file
Open BatFileName For Output As #1
Print #1, "Copy " & Chr(34) & foldername & "*.csv" _
& Chr(34) & " " & TXTFileName
Close #1
'Run the Bat file to collect all data from the CSV files into a TXT file
ShellAndWait BatFileName, 0
If Dir(TXTFileName) = "" Then
MsgBox "There are no csv files in this folder"
Kill BatFileName
Exit Sub
End If
'Open the TXT file in Excel
Application.ScreenUpdating = False
Workbooks.OpenText Filename:=TXTFileName, Origin:=xlWindows, StartRow _
:=1, DataType:=xlDelimited, TextQualifier:=xlDoubleQuote, _
ConsecutiveDelimiter:=False, Tab:=False, Semicolon:=False, Comma:=True, _
Space:=False, Other:=False
'Save text file as a Excel file
Set Wb = ActiveWorkbook
Application.DisplayAlerts = False
Wb.SaveAs Filename:=XLSFileName, FileFormat:=FileFormatNum
Application.DisplayAlerts = True
Wb.Close savechanges:=False
MsgBox "You find the Excel file here: " & vbNewLine & XLSFileName
'Delete the bat and text file you temporary used
Kill BatFileName
Kill TXTFileName
Application.ScreenUpdating = True
End If
End Sub
You'll find more info here.
http://www.rondebruin.nl/win/s3/win021.htm
If you want to maintain the data connections, like I do, then you need to first make a query using Power Query M formula. Then you can add your connection to that query. This is what Excel does when you use the Get Data wizards.
Use this procedure in your loop and it will make a new sheet for each CSV file:
'#Description("Create a new worksheet with a table that is connected to a CSV file as a data source.")
Public Sub GetDataFromCSV(ByVal name As String, ByVal fileName As String)
On Error GoTo errorHandler
' The Power Query points to the CSV file, if your data contains headers you need the Promoted Headers
Dim csvFormula As String
csvFormula = "let" & vbNewLine & _
" Source = Csv.Document(File.Contents(""" & fileName & """),null,null,null,1252)," & vbNewLine & _
" #""Promoted Headers"" = Table.PromoteHeaders(Source, [PromoteAllScalars=true])" & vbNewLine & _
"in" & vbNewLine & _
" #""Promoted Headers"""
ThisWorkbook.Queries.Add name:=name, Formula:=csvFormula
' The workbook connects to that query
Dim newConnection As WorkbookConnection
Set newConnection = ThisWorkbook.Connections.Add2("Query - " & name, _
"Connection to the '" & name & "' query in the workbook.", _
"OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=" & name & ";Extended Properties="""";", """" & name & """", 6, True, False)
' I always want one table per sheet that begins at A1
Dim newSheet As Worksheet
Set newSheet = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
newSheet.name = name
' Only once you have the connection backed by the query can you link it to a Table
With newSheet.ListObjects.Add(SourceType:=xlSrcModel, Source:=newConnection, LinkSource:=True, XlListObjectHasHeaders:=xlYes, Destination:=newSheet.Range("$A$1")).TableObject
.RowNumbers = False
.PreserveFormatting = True
.RefreshStyle = 1
.AdjustColumnWidth = True
' I get errors when there are hyphens in the DisplayName, the default behavior of the wizard replaces them with underscors
.ListObject.DisplayName = Replace(name, "-", "_")
.Refresh
End With
Exit Sub
errorHandler:
If Err.Number = -2147024809 Then
' Query already exists, we delete it so we can recreate it
ThisWorkbook.Queries.Item(name).Delete
Resume
Else
Debug.Print "ERROR " & Err.Number & " : " & Err.Description
End If
End Sub

Get CPU Name, Architecture and Clock Speed in VB

How do I find my CPU Name, Architecture and Clock Speed as a string in Visual Basic? I want it to display like it does in System Properties: Intel(R) Core(TM) i5-3210M CPU # 2.50GHz
I need the answer in VB.net. I have found other answers in C#, C++, C and Java.
VBA cannot do that directly, but you can invoke the Windows Management Interface.
Please see the information from http://www.asap-utilities.com/excel-tips-detail.php?categorie=9&m=78 (copied below)
Sub ProcessorSpeed()
' shows the processor name and speed of the computer
Dim MyOBJ As Object
Dim cpu As Object
Set MyOBJ = GetObject("WinMgmts:").instancesof("Win32_Processor")
For Each cpu In MyOBJ
MsgBox(cpu.Name.ToString + " " + cpu.CurrentClockSpeed.ToString + " Mhz", vbInformation)
Next
End Sub
i use a similar routine for my use.
Option Explicit
Private Declare PtrSafe Sub GetSystemInfo Lib "kernel32" (lpSystemInfo As SYSTEM_INFO)
Type SYSTEM_INFO
dwOemID As Long
dwPageSize As Long
lpMinimumApplicationAddress As Long
lpMaximumApplicationAddress As Long
dwActiveProcessorMask As Long
dwNumberOfProcessors As Long
dwProcessorType As Long
dwAllocationGranularity As Long
dwReserved As Long
End Type
'Sub DisplaySystemInfo()
' Dim lpSysInfo As SYSTEM_INFO
' GetSystemInfo lpSysInfo
' Debug.Print "Number of processors: " & lpSysInfo.dwNumberOfProcessors
' Debug.Print "Processor type: " & lpSysInfo.dwProcessorType
'End Sub
Function GetCPUData() As String
Dim result As String
Dim objCPUItem As Object
Dim objCPU As Object
Err.Clear
On Error Resume Next
Set objCPUItem = GetObject("winmgmts:").InstancesOf("Win32_Processor")
If Err.Number <> 0 Then
result = "Error getting Win32_Processor " & _
"information." & vbCrLf
Else
result = "Number of processors incl. Co-CPUs: " & Trim$(str$(objCPUItem.Count)) & vbCrLf & vbCrLf
For Each objCPU In objCPUItem
result = result & "Processor: " & objCPU.DeviceID & vbCrLf
result = result & "Description: " & Trim$(objCPU.Name) & vbCrLf
result = result & "Frequency (MHz): " & objCPU.MaxClockSpeed & vbCrLf
result = result & "CPU-ID: " & objCPU.ProcessorId & vbCrLf
result = result & vbCrLf
Next
Set objCPUItem = Nothing
End If
On Error GoTo 0
GetCPUData = result
End Function
Function cpu3() As Long 'this sets the multi threading to max number of cpu used by excel
With Application
.MultiThreadedCalculation.Enabled = True
.MultiThreadedCalculation.ThreadMode = xlThreadModeAutomatic 'set to max cores
cpu3 = .MultiThreadedCalculation.ThreadCount
.MultiThreadedCalculation.ThreadMode = xlThreadModeManual
End With
End Function
Sub testinggg()
Dim strComputer$
Dim i&
Dim objWMIService As Object, colItems As Object, objItem As Object
strComputer = "." 'Local machine, can be adjusted to access remote workstations
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery( _
"SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor", , 48)
For Each objItem In colItems
i = i + 1 '1=cpu all
Debug.Print "-----------------------------------"
Debug.Print "Processor " & i
'Debug.Print "-----------------------------------"
Debug.Print "usage: " & objItem.PercentProcessorTime
Next
Set objWMIService = Nothing
Set colItems = Nothing
Set objItem = Nothing
End Sub
Works for me on Win 7 HB x64
MsgBox CreateObject("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0\ProcessorNameString")