VBA Macro gets slower with every execution - performance

I have a macro that reads a .txt file line by line. I check every line if it's equal to some code for a new page, say it's "NEXT" - if yes, then a page break is inserted. After certain amount of "NEXT" occurences the whole document gets exported to pdf. Then the content of .doc gets deleted and I continue reading & exporting the txt file until EOF.
Problem: macro gets slower with every execution.
My test file has 27300 lines / 791 kB (real files are somewhere between 10 and 100MB). Before I start the macro, the WINWORD process takes 40K of memory. The memory usage gets bigger after every execution of the macro.
Time Max.MemoryUsage MemoryUsageAfterwards
Run1 11.9s 70K 64K
Run2 19.7s 90K 84K
Run3 22.3s 99K 92K
I know a partial solution is to close and reopen the .doc file manually and run the macro with next .txt file as input. However, it takes long time to even close the Word after one run of macro, although there is no content in the file that I can see.
What I'm asking is if there is another way to solve this, what I believe is to be a memory clearing issue?
My code:
When the document is opened:
Private Sub Document_Open()
ReadAndSplit
End Sub
Global variables and declarations:
Option Explicit
'---------------------------------------------------------------------------
' GLOBAL VARIABLES
'---------------------------------------------------------------------------
Public numOfBreaks As Integer ' number of page breaks made
Public numOfPdfs As Integer ' number of currently printed pdf
Public filePrefix As String ' name prefix for .pdf files
Public sFileName As String ' name of Input File
Public breakAfter As Integer ' print after this number of NEXT
Public cancelActive As Boolean ' cancel Button pressed? (for exit)
Main macro:
Sub ReadAndSplit()
'---------------------------------------------------------------------------
' VARIABLES
'---------------------------------------------------------------------------
Dim sLine As String ' line from text file
Dim numOfLines As Long ' number of lines read from .txt input
Dim execStart As Single ' starting time of script execution
Dim nextPage As Boolean ' indicates if new document beginns
'---------------------------------------------------------------------------
' INITIAL PROCESSING
'---------------------------------------------------------------------------
Application.Visible = False
Application.ScreenUpdating = False
Selection.WholeStory ' clear the document
Selection.Delete
UserForm1.Show ' show user dialog
If cancelActive Then ' Cancel button handling
Application.Visible = True
Exit Sub
End If
With ActiveDocument.PageSetup ' set page margins & orientation
.TopMargin = 0.1
.BottomMargin = 0.1
.LeftMargin = 0.1
.RightMargin = 0.1
End With
'---------------------------------------------------------------------------
' MAIN PROCESSING
'---------------------------------------------------------------------------
numOfBreaks = 0 ' GLOBALS
numOfPdfs = 1
numOfLines = 0 ' LOCALS
nextPage = True
execStart = Timer
Open sFileName For Input As #1
Do While Not EOF(1)
If nextPage Then ' write 2 empty lines
Selection.TypeText (vbNewLine & vbNewLine)
nextPage = False
End If
Line Input #1, sLine ' read 1 line from input
numOfLines = numOfLines + 1 ' count lines
If sLine <> "NEXT" Then ' test for NEXT
Selection.TypeText (sLine) & vbNewLine ' write line from input .txt
Else
Selection.InsertBreak Type:=wdPageBreak ' NEXT -> new page
numOfBreaks = numOfBreaks + 1 ' count new receipts
If numOfBreaks = breakAfter Then ' compare with PARAM
PrintAsPDF ' export to pdf
numOfBreaks = 0
End If
nextPage = True ' switch new page on
End If
Loop
If numOfBreaks <> 0 Then ' print out the last part
PrintAsPDF
End If
Close #1
Debug.Print vbNewLine & "-----EXECUTION-----"
Debug.Print Now
Debug.Print "Lines: " & numOfLines
Debug.Print "Time: " & (Timer - execStart)
Debug.Print "-------------------" & vbNewLine
Selection.WholeStory ' clear the word document
Selection.Delete
Application.Visible = True
End Sub
Macro used for printing PDF:
Sub PrintAsPDF()
Dim newPdfFileName As String ' path + name for current .pdf file
newPdfFileName = ActiveDocument.Path & "\" & filePrefix & "-" & numOfPdfs & ".pdf"
Selection.WholeStory ' set font
With Selection.Font
.Name = "Courier New"
.Size = 10.5
End With
ActiveDocument.SaveAs2 newPdfFileName, 17
numOfPdfs = numOfPdfs + 1
Selection.WholeStory
Selection.Delete
End Sub
UserForm code:
'---------------------------------------------------------------------------
' OK BUTTON
'---------------------------------------------------------------------------
Private Sub OKButton_Click()
Dim inputFileOk As Boolean ' input file path
Dim inputSplitOk As Boolean ' split
Dim prefixOk As Boolean ' prefix
If FileTxtBox.Text = vbNullString Then ' validate file path
inputFileOk = False
MsgBox ("File path missing!")
Else
inputFileOk = True
End If
If IsNumeric(SplitTxtBox.Text) Then ' validate breakAfter
breakAfter = SplitTxtBox.Text
inputSplitOk = True
Else
MsgBox ("Non-numeric value for SPLIT!")
End If
If PrefixTxtBox <> vbNullString Then ' validate prefix
filePrefix = PrefixTxtBox.Text
prefixOk = True
Else
MsgBox ("Missing prefix!")
End If
' check if all inputs are ok
If inputFileOk And inputSplitOk And prefixOk Then
cancelActive = False
Unload Me
End If
End Sub
'---------------------------------------------------------------------------
' CANCEL BUTTON
'---------------------------------------------------------------------------
Private Sub CancelButton_Click()
cancelActive = True ' for script termination
Unload Me
End Sub
'---------------------------------------------------------------------------
' FILE BUTTON
'---------------------------------------------------------------------------
Private Sub FileButton_Click()
Dim i As Integer ' file selection index
' show file chooser dialog and assign selected file to sFileName
Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = False
i = Application.FileDialog(msoFileDialogOpen).Show
If i <> 0 Then
sFileName = Application.FileDialog(msoFileDialogOpen).SelectedItems(1)
FileTxtBox.Text = sFileName
End If
End Sub

Word stores a lot of information in "temp" files in order to track "unlimited" Undo. If you perform a lot of actions without saving the file or clearing the Undo buffer, this slows Word down. I recommend, therefore:
Clear the Undo buffer (ActiveDocument.UndoClear)
Save the (empty) document periodically
in order to free resources.

Related

How to recover the outlook emails for a deleted email account

I accidentally delete my email account and so all of the emails in that account. Is there a chance to recover the emails? How can I recover it? Thanks.
Four months ago I would have agreed with Om3r's comment giving the location of the Outlook stores. But I bought a new laptop in December and now the Outlook files are not where all the documentation says they should be. Worse, I cannot reach the folders containing the Outlook files using File Explorer although I can find them with VBA.
The macro below searches drive C for files with an extension of OST or PST. I cannot promise this macro will find your lost store but, if it is still on your disc, it will find it. If you find the missing store, you will probably have to use VBA to move it to somewhere accessible.
Copy the macro below to a macro-enabled workbook and run it. While it is running the active worksheet will look like:
1923 Folders to search
327 Folders searched
Store Size Date Folder
$ILJARJ0.pst 212 28Mar20 C:\$Recycle.Bin\S-1-5-21-3957073674-21115239-22921093-1001
$IMS96DJ.pst 212 28Mar20 C:\$Recycle.Bin\S-1-5-21-3957073674-21115239-22921093-1001
The top two rows give a crude progress indicator. On my laptop, the routine ends with 69190 folders searched. I do not know why there are PST files in my recycle bin. I did nothing relevant on 28 March. When the routine has finished, there will be a auto-fitted list of every store the macro found. On my laptop none are where I would expect and some are duplicates. I hope you find your store.
Option Explicit
Sub SearchForStoresOnC()
' Searches drive C for files with an extension of PST or OST
' Warning: overwrites the active workbook
Dim ErrNum As Long
Dim FileAttr As Long
Dim FileName As String
Dim FldrName As String
Dim RowCrnt As Long
Dim ToSearch As Collection
Cells.EntireRow.Delete
Range("A1").Value = 0
Range("A2").Value = 0
Range("B1").Value = "Folders to search"
Range("B2").Value = "Folders searched"
Range("B4").Value = "Store"
With Range("C4")
.Value = "Size"
.HorizontalAlignment = xlRight
End With
With Range("D4")
.Value = "Date"
.HorizontalAlignment = xlRight
End With
Range("E4") = "Folder"
RowCrnt = 5
Set ToSearch = New Collection
' Load ToSearch with drive to search.
ToSearch.Add "C:"
Do While ToSearch.Count > 0
FldrName = ToSearch(1)
ToSearch.Remove 1
Err.Clear
ErrNum = 0
On Error Resume Next
' Stores are unlikely to be hidden but can be in folders that are hidden
FileName = Dir$(FldrName & "\*.*", vbDirectory + vbHidden + vbSystem)
ErrNum = Err.Number
On Error GoTo 0
If ErrNum <> 0 Then
'Debug.Print "Dir error: " & FldrName
Else
Do While FileName <> ""
If FileName = "." Or FileName = ".." Then
' Ignore pointers
Else
Err.Clear
On Error Resume Next
FileAttr = GetAttr(FldrName & "\" & FileName)
ErrNum = Err.Number
On Error GoTo 0
If ErrNum = 0 Then
' Ignore file and folders which give errors
If (FileAttr And vbDirectory) = 0 Then
' File
'Debug.Assert False
Select Case Right$(FileName, 4)
Case ".pst", ".ost"
Cells(RowCrnt, "B").Value = FileName
With Cells(RowCrnt, "C")
.Value = FileLen(FldrName & "\" & FileName)
.NumberFormat = "#,##0"
End With
With Cells(RowCrnt, "D")
.Value = FileDateTime(FldrName & "\" & FileName)
.NumberFormat = "dmmmyy"
End With
Cells(RowCrnt, "E").Value = FldrName
RowCrnt = RowCrnt + 1
End Select
Else
' Directory
ToSearch.Add FldrName & "\" & FileName
End If ' File or Directory
Else
'Debug.Print "FileAttr error: " & FldrName & "\" & FileName
End If ' FileAttr does not give an error
End If ' Pointer or (File or Directory)
FileName = Dir$
Loop ' For each pointer, file and sub-directory in folder
End If ' Dir$ gives error
Range("A1") = ToSearch.Count
Range("A2") = Range("A2") + 1
DoEvents
Loop 'until ToSearch empty
Columns.AutoFit
End Sub

Populate GPO from Text File using VBScript or other

Ok, so we need to create a GPO that allows our users to only use specific programs.
GPO Location:
User Configuration
Policies
Administrative Templates [...]
System
Run only specified Windows applications
Then setting the GPO to enabled and clicking on List of allowed applications --> Show...
I have created an excel spreadsheet containing the names of all the programs and their associated executable files with other pertinent information so that we can easily organize, add, delete, etc. the executable files that we need to allow our users access to.
This spreadsheet then dumps all the executable files into a text file.
Here is an example of what the text file looks like:
Acrobat.exe
chrome.exe
calc.exe
.
.
.
There are a lot of entries and these are likely subject to change. What I am trying to do is create a script that will take that text file and populate the GPO automatically. I don't care if we have to open the window and then run it, it does not need to run from the task scheduler (although that would be amazing if someone has that code ready). We just need it to populate this ridiculous amount of executable filenames into the fields.
Here is code I found (VBScript) that when run, should populate the fields automatically, however I cannot get it to run in the Group Policy Management Editor (it runs in the windows explorer window instead and ends up searching for some of the files)
' Open the text file, located in the same path as the script
Set objFSO = CreateObject("Scripting.FileSystemObject")
strPath = Mid(Wscript.ScriptFullName, 1, InStrRev(Wscript.ScriptFullName, wscript.ScriptName) -1)
Set objFile = objFSO.OpenTextFile(strPath & "appList.txt")
' Activate the "Show Contents" window with the "List of allowed applications".
' Note the window must be opened already and we should have selected where in
' the list we want to enter the data before running the script
set WshShell = WScript.CreateObject("WScript.Shell")
WScript.Sleep 1000
WshShell.AppActivate "Show Contents"
' Read the file line by line
Do While objFile.AtEndOfStream <> True
' Each line contains one EXE name
exeName = objFile.ReadLine
' Escape forbidden chars { } [ ] ( ) + ^ % ~
exeName = Replace(exeName, "[", "{[}")
exeName = Replace(exeName, "]", "{]}")
exeName = Replace(exeName, "(", "{(}")
exeName = Replace(exeName, ")", "{)}")
exeName = Replace(exeName, "+", "{+}")
exeName = Replace(exeName, "^", "{^}")
exeName = Replace(exeName, "%", "{%}")
exeName = Replace(exeName, "~", "{~}")
' Send the EXE name to the window
WScript.Sleep 100
WshShell.SendKeys exeName
' Move to the next one
WshShell.SendKeys "{TAB}"
Loop
objFile.Close
from: http://blogs.msdn.com/b/alejacma/archive/2011/03/24/how-to-update-quot-run-only-specified-windows-applications-quot-gpo-programmatically-vbscript.aspx
"C:\Windows\System32\GroupPolicy\User\Registry.pol"
Is where my policies are stored. It's a semi text file. Try writing to that file.
Ok, so I tried it many different ways. If anyone is looking for an answer to do this, this is the way I've figured it out and the way I've decided to proceed. I will post all relevant code below.
In Excel, the format of my table is as follows:
(With obviously WAY more entries)
Here is the VBA code I used to turn the data from this file into the proper format for the registry key:
VBA - In Excel
Public Sub ExportToTextFile(FName As String, _
Sep As String, SelectionOnly As Boolean, _
AppendData As Boolean)
Dim WholeLine As String
Dim FNum As Integer
Dim RowNdx As Long
Dim ColNdx As Integer
Dim StartRow As Long
Dim EndRow As Long
Dim StartCol As Integer
Dim EndCol As Integer
Dim CellValue As String
Application.ScreenUpdating = False
On Error GoTo EndMacro:
FNum = FreeFile
StartRow = 2
If SelectionOnly = True Then
With Selection
StartCol = .Cells(2).Column
EndRow = .Cells(.Cells.Count).Row
EndCol = .Cells(2).Column
End With
Else
With ActiveSheet.UsedRange
StartCol = .Cells(2).Column
EndRow = .Cells(.Cells.Count).Row
EndCol = .Cells(2).Column
End With
End If
If AppendData = True Then
Open FName For Append Access Write As #FNum
Else
Open FName For Output Access Write As #FNum
End If
For RowNdx = StartRow To EndRow
WholeLine = ""
For ColNdx = StartCol To EndCol
If Cells(RowNdx, ColNdx).Value = "" Then
CellValue = ""
Else
CellValue = Cells(RowNdx, ColNdx).Value
End If
WholeLine = WholeLine & Chr(34) & CellValue & ".exe" & Chr(34) & "=" & Chr(34) & CellValue & ".exe" & Chr(34) & Sep
Next ColNdx
WholeLine = Left(WholeLine, Len(WholeLine) - Len(Sep))
Print #FNum, WholeLine; ""
Next RowNdx
EndMacro:
On Error GoTo 0
Application.ScreenUpdating = True
Close #FNum
End Sub
Sub PipeExport()
Dim FileName As Variant
Dim Sep As String
FileName = Application.GetSaveAsFilename(InitialFileName:="appList", filefilter:="Text (*.txt),*.txt")
If FileName = False Then
''''''''''''''''''''''''''
' user cancelled, get out
''''''''''''''''''''''''''
Exit Sub
End If
Sep = "|"
If Sep = vbNullString Then
''''''''''''''''''''''''''
' user cancelled, get out
''''''''''''''''''''''''''
Exit Sub
End If
Debug.Print "FileName: " & FileName, "Extension: " & Sep
ExportToTextFile FName:=CStr(FileName), Sep:=CStr(Sep), _
SelectionOnly:=False, AppendData:=False
End Sub
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
PipeExport
End Sub
The file that is created is appList.txt and its format is the same format as the registry key:
"Acrobat.exe"="Acrobat.exe"
"AcroRd32.exe"="AcroRd32.exe"
Now in your GPO, add a unique program name to the allowed applications list (say test1234.exe) and in your registry editor, go to Edit > Find test1234.exe.
Export that registry key under File > Export. Remove the test1234.exe line and paste in your text file. Then reimport that file and you're done!

Sending data to device via MS Comm VB6

I need to send a file to some electronic device and execute it.
I couldn't find any information online regarding MS Comms and I didn't find Documentation on the Microsoft (https://msdn.microsoft.com/en-us/library/aa231237(v=vs.60).aspx) any useful :
' Send Byte array data
MSComm1.Output = Out
Would be great if you guys could give me some pointers and help me to solve my problem. The problem that I am experiencing is an infinite loop at Loop Until MSComm1.OutBufferCount = 0, when I return "MSComm1.OutBufferCount" between Do and Loop "MSComm1.OutBufferCount" is 0 and files dont seem to be sent over to the device.
Closest function I got to at the present moment is below:
Function SendFile(tmp$)
Dim temp$
Dim hsend, bsize, LF&
' Open file
Open tmp$ For Binary Access Read As #2
' Check size on Mscomm1 OutBuffer
bsize = MSComm1.OutBufferSize
' Check file length
LF& = LOF(2)
' This code makes tiny pieces of data (Buffer sized)
' And send's it
Do Until EOF(2)
If LF& - Loc(2) <= bsize Then
bsize = LF& - Loc(2) + 1
End If
' Make room for some data
temp$ = Space$(bsize)
' Put the data piece in the Temp$ string
Get #2, , temp$
MSComm1.Output = temp$
Do
' Wait until the buffer is empty
Loop Until MSComm1.OutBufferCount = 0
Loop
' close file
Close #2
End Function
Have a look at the RThreshold and SThreshold properties
Below is a simple example project :
'1 form with :
' 1 label control : name=Label1
' 1 textbox control : name=Text1
' 1 command button : name=Command1
Option Explicit
Private Sub Command1_Click()
'send command
MSComm1.Output = Text1.Text & vbCr
End Sub
Private Sub Form_Load()
'config mscomm control and open connection
With MSComm1
.Settings = "9600,N,8,1"
.RThreshold = 1
.SThreshold = 0
.CommPort = 1
.PortOpen = True
End With 'MSComm1
End Sub
Private Sub Form_Resize()
'position controls
Dim sngWidth As Single, sngHeight As Single
Dim sngCmdWidth As Single, sngCmdHeight As Single
Dim sngTxtWidth As Single
Dim sngLblHeight As Single
sngWidth = ScaleWidth
sngHeight = ScaleHeight
sngCmdWidth = 1215
sngCmdHeight = 495
sngLblHeight = sngHeight - sngCmdHeight
sngTxtWidth = sngWidth - sngCmdWidth
Label1.Move 0, 0, sngWidth, sngLblHeight
Text1.Move 0, sngLblHeight, sngTxtWidth, sngCmdHeight
Command1.Move sngTxtWidth, sngLblHeight, sngCmdWidth, sngCmdHeight
End Sub
Private Sub MSComm1_OnComm()
'process received data
Dim strInput As String
Select Case MSComm1.CommEvent
Case comEvReceive
strInput = MSComm1.Input
Label1.Caption = Label1.Caption & strInput
End Select
End Sub
In Command1_Click I add a carriage return to the command from Text1 as most devices require the command to be finished by that
In MSComm1_OnComm I just print the received data to the label, but you might want to add the received data to a global variable, and then process the contents of that variable, as all data might not be received at once

VBSCRIPT PPT conversion script

I'm attempting to convert PPT files to PPTX files using VBSCRIPT. I haven't used VB in a very long time & am pretty unfamiliar with the framework. I'm attempting to modify a script that converts PPTX/PPT to PDF, however without much luck. Here's an example of what I've got so far...
Option Explicit
Dim inputFile
Dim objPPT
Dim objPresentation
Dim objPrintOptions
Dim objFso
Dim pptf
If WScript.Arguments.Count <> 1 Then
WriteLine "You need to specify input and output files."
WScript.Quit
End If
inputFile = WScript.Arguments(0)
Set objFso = CreateObject("Scripting.FileSystemObject")
If Not objFso.FileExists( inputFile ) Then
WriteLine "Unable to find your input file " & inputFile
WScript.Quit
End If
WriteLine "Input File: " & inputFile
Set objPPT = CreateObject( "PowerPoint.Application" )
objPPT.Visible = True
objPPT.Presentations.Open inputFile
Set objPresentation = objPPT.ActivePresentation
objPresentation.SaveAs "out.pptx", Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsOpenXMLPresentation
objPresentation.Close
ObjPPT.Quit
Things turn pear shaped around the objPresentation.SaveAs line; obviously its illegal syntax - however I'm not sure of the best route here. Any help would be much appreciated. Also if there are other variables (or a link to api documentation) for converting doc->docx, and xls->xlsx.
Thanks in advance.
EDIT:
I found a solution to this myself; sorry I stopped checking in on this thread a few days after posted it. I found a documentation page for this code & noticed one function in particular (convert2): http://msdn.microsoft.com/en-us/library/office/ff743830.aspx
I'll mark the answer below as the answer; because it came first (although I haven't tested it). If you're interested - heres my code; AFAIK it only converts in between various PowerPoint formats (in either direction). Also FYI I modified this script from another popularly googlized script on the topic; the only line I changed was one of the last (the convert2 mehtod). Anyways... (also - this requires office 2010; per the documentation)
Usage:
CSCRIPT scriptName.vbs C:\inputfileName.ppt C:\outputFileName.pptx
Option Explicit
Sub WriteLine ( strLine )
WScript.Stdout.WriteLine strLine
End Sub
' http://msdn.microsoft.com/en-us/library/office/aa432714(v=office.12).aspx
Const msoFalse = 0 ' False.
Const msoTrue = -1 ' True.
' http://msdn.microsoft.com/en-us/library/office/bb265636(v=office.12).aspx
Const ppFixedFormatIntentScreen = 1 ' Intent is to view exported file on screen.
Const ppFixedFormatIntentPrint = 2 ' Intent is to print exported file.
' http://msdn.microsoft.com/en-us/library/office/ff746754.aspx
Const ppFixedFormatTypeXPS = 1 ' XPS format
Const ppFixedFormatTypePDF = 2 ' PDF format
' http://msdn.microsoft.com/en-us/library/office/ff744564.aspx
Const ppPrintHandoutVerticalFirst = 1 ' Slides are ordered vertically, with the first slide in the upper-left corner and the second slide below it.
Const ppPrintHandoutHorizontalFirst = 2 ' Slides are ordered horizontally, with the first slide in the upper-left corner and the second slide to the right of it.
' http://msdn.microsoft.com/en-us/library/office/ff744185.aspx
Const ppPrintOutputSlides = 1 ' Slides
Const ppPrintOutputTwoSlideHandouts = 2 ' Two Slide Handouts
Const ppPrintOutputThreeSlideHandouts = 3 ' Three Slide Handouts
Const ppPrintOutputSixSlideHandouts = 4 ' Six Slide Handouts
Const ppPrintOutputNotesPages = 5 ' Notes Pages
Const ppPrintOutputOutline = 6 ' Outline
Const ppPrintOutputBuildSlides = 7 ' Build Slides
Const ppPrintOutputFourSlideHandouts = 8 ' Four Slide Handouts
Const ppPrintOutputNineSlideHandouts = 9 ' Nine Slide Handouts
Const ppPrintOutputOneSlideHandouts = 10 ' Single Slide Handouts
' http://msdn.microsoft.com/en-us/library/office/ff745585.aspx
Const ppPrintAll = 1 ' Print all slides in the presentation.
Const ppPrintSelection = 2 ' Print a selection of slides.
Const ppPrintCurrent = 3 ' Print the current slide from the presentation.
Const ppPrintSlideRange = 4 ' Print a range of slides.
Const ppPrintNamedSlideShow = 5 ' Print a named slideshow.
' http://msdn.microsoft.com/en-us/library/office/ff744228.aspx
Const ppShowAll = 1 ' Show all.
Const ppShowNamedSlideShow = 3 ' Show named slideshow.
Const ppShowSlideRange = 2 ' Show slide range.
'
' This is the actual script
'
Dim inputFile
Dim outputFile
Dim objPPT
Dim objPresentation
Dim objPrintOptions
Dim objFso
If WScript.Arguments.Count <> 2 Then
WriteLine "You need to specify input and output files."
WScript.Quit
End If
inputFile = WScript.Arguments(0)
outputFile = WScript.Arguments(1)
Set objFso = CreateObject("Scripting.FileSystemObject")
If Not objFso.FileExists( inputFile ) Then
WriteLine "Unable to find your input file " & inputFile
WScript.Quit
End If
If objFso.FileExists( outputFile ) Then
WriteLine "Your output file (' & outputFile & ') already exists!"
WScript.Quit
End If
WriteLine "Input File: " & inputFile
WriteLine "Output File: " & outputFile
Set objPPT = CreateObject( "PowerPoint.Application" )
objPPT.Visible = True
objPPT.Presentations.Open inputFile
Set objPresentation = objPPT.ActivePresentation
Set objPrintOptions = objPresentation.PrintOptions
objPrintOptions.Ranges.Add 1,objPresentation.Slides.Count
objPrintOptions.RangeType = ppShowAll
' Reference for this at http://msdn.microsoft.com/en-us/library/office/ff746080.aspx
objPresentation.convert2(output)
objPresentation.Close
ObjPPT.Quit
Normally you would do this in PowerPoint with ExportAsFixedFormat(...). Since you chose VBS, you have to use SaveAs(...).
I assume you would also want to be able to batch convert ppt/pptx into pdf rather than specify a full file name one by one...
Option Explicit
'http://msdn.microsoft.com/en-us/library/office/bb251061(v=office.12).aspx
Const ppSaveAsPDF = 32
Dim oFSO ' Public reference to FileSystemObject
Dim oPPT ' Public reference to PowerPoint App
Main
Sub Main()
Dim sInput
If wscript.Arguments.Count <> 1 Then
Wscript.Echo "You need to specify input and output files."
wscript.Quit
End If
' PowerPoint version must be 12 or later (PowerPoint 2007 or later)
Set oPPT = CreateObject("PowerPoint.Application")
If CDbl(oPPT.Version) < 12 Then
Wscript.Echo "PowerPoint version must be 2007 or later!"
oPPT.Visible = True
oPPT.Quit
Set oPPT = Nothing
wscript.Quit
End If
' Store Input Argument and detect execute mode (single file / Folder batch mode)
sInput = wscript.Arguments(0)
Set oFSO = CreateObject("Scripting.FileSystemObject")
If IsPptFile(sInput) Then
PPT2PDF sInput
ElseIf oFSO.FolderExists(sInput) Then
Wscript.Echo "Batch Start: " & Now
Wscript.Echo "Root Folder: " & sInput
BatchPPT2PDF sInput
Else
Wscript.Echo """" & sInput & """ is not a PPT file or Folder!"
End If
' Close PowerPoint app if no other presentations are opened
If oPPT.Presentations.Count = 0 Then oPPT.Quit
Set oPPT = Nothing
Set oFSO = Nothing
End Sub
Private Sub BatchPPT2PDF(sFDR)
Dim oFDR, oFile
Wscript.Echo String(50, Chr(151))
Wscript.Echo "Processing Folder: " & sFDR
For Each oFile In oFSO.GetFolder(sFDR).Files
If IsPptFile(oFile.Name) Then
PPT2PDF(oFile)
End If
Next
For Each oFDR In oFSO.GetFolder(sFDR).SubFolders
BatchPPT2PDF oFDR
Next
End Sub
Private Function IsPptFile(sFile)
IsPptFile = (InStr(1, Right(sFile, InStrRev(sFile, ".")), "ppt") > 0)
End Function
Private Sub PPT2PDF(sFile)
On Error Resume Next
Dim sPDF, oPres
sPDF = Left(sFile,InstrRev(sFile,".")) & "pdf"
Set oPres = oPPT.Presentations.Open(sFile, True, False, False) ' Read Only, No Title, No Window
Err.Clear
oPres.SaveAs sPDF, ppSaveAsPDF
oPres.Close
Set oPres = Nothing
If Err.Number = 0 Then
Wscript.Echo "OK" & vbTab & sPDF
Else
Wscript.Echo "X" & vbTab & sPDF & " [ERR " & Err.Number & ": " & Err.Description & "]"
Err.Clear
End If
End Sub

VBA: Microsoft Word process does not exit after combining many Word files into one

I'm trying to merge many Word files into one. I am doing this inside a VBA routine in MS Excel. The Word files are all in a folder named "files" and I want to create a new file "combinedfile.docx" in a folder one-level above that. The problem I'm facing is regarding how the Word process behaves after merging the files (whether or not it exits after the execution of the VBA function). On some machines, this process works fine (except that it has page 2 and the last page as blank), while on some others, the merged document contains a blank page and the Process Manager shows the Word process started by the VBA function as still running.
I am not used to VBA programming and as you can see in the code below, I don't know the right way to close an open document and exit a open Word process. If anyone could look at what I've done and suggest a way to solve this problem, it would be very helpful.
I am also interested to know if this is the right way to merge several Word files. If there's a better way, please let me know.
'the flow:
' start a word process to create a blank file "combinedfile.docx"
' loop over all documents in "files" folder and do the following:
' open the file, insert it at the end of combinedfile.docx, then insert pagebreak
' close the file and exit the word process
filesdir = ActiveWorkbook.Path + "\" + "files\"
thisdir = ActiveWorkbook.Path + "\"
singlefile = thisdir + "combinedfile.docx"
'if it already exists, delete
If FileExists(singlefile) Then
SetAttr singlefile, vbNormal
Kill singlefile
End If
Dim wordapp As Word.Application
Dim singledoc As Word.Document
Set wordapp = New Word.Application
Set singledoc = wordapp.Documents.Add
wordapp.Visible = True
singledoc.SaveAs Filename:=singlefile
singledoc.Close 'i do both this and the line below (is it necessary?)
Set singledoc = Nothing
wordapp.Quit
Set wordapp = Nothing
JoinFiles filesdir + "*.docx", singlefile
Sub JoinFiles(alldocs As String, singledoc As String)
Dim wordapp As Word.Application
Dim doc As Word.Document
Set wordapp = New Word.Application
Set doc = wordapp.Documents.Open(Filename:=singledoc)
Dim filesdir As String
filesdir = ActiveWorkbook.Path + "\" + "files\"
docpath = Dir(alldocs, vbNormal)
While docpath ""
doc.Bookmarks("\EndOfDoc").Range.InsertFile (filesdir + docpath)
doc.Bookmarks("\EndOfDoc").Range.InsertBreak Type:=wdPageBreak
docpath = Dir
Wend
doc.Save
doc.Close
Set doc = Nothing
wordapp.Quit
Set wordapp = Nothing
End Sub
I propose to optimize your code in following ways:
open the WordApp only once and move files into it without closing/reopening
no need to kill combineddoc upfront, it will be simply overwritten by the new file
no need for a Word.Document object, all can be done in the Word.Application object
so the code gets a lot simpler:
Sub Merge()
Dim WordApp As Word.Application
Dim FilesDir As String, ThisDir As String, SingleFile As String, DocPath As String
Dim FNArray() As String, Idx As Long, Jdx As Long ' NEW 11-Apr-2013
FilesDir = ActiveWorkbook.Path + "\" + "files\"
ThisDir = ActiveWorkbook.Path + "\"
SingleFile = ThisDir + "combinedfile.docx"
Set WordApp = New Word.Application
' NEW 11-Apr-2013 START
' read in into array
Idx = 0
ReDim FNArray(Idx)
FNArray(Idx) = Dir(FilesDir & "*.docx")
Do While FNArray(Idx) <> ""
Idx = Idx + 1
ReDim Preserve FNArray(Idx)
FNArray(Idx) = Dir()
Loop
ReDim Preserve FNArray(Idx - 1) ' to get rid of last blank element
BubbleSort FNArray
' NEW 11-Apr-2013 END
With WordApp
.Documents.Add
.Visible = True
' REMOVED 11-Apr-2013 DocPath = Dir(FilesDir & "*.docx")
' REMOVED 11-Apr-2013 Do While DocPath <> ""
' REMOVED 11-Apr-2013 .Selection.InsertFile FilesDir & DocPath
' REMOVED 11-Apr-2013 .Selection.TypeBackspace
' REMOVED 11-Apr-2013 .Selection.InsertBreak wdPageBreak
' REMOVED 11-Apr-2013 DocPath = Dir
' REMOVED 11-Apr-2013 Loop
' NEW 11-Apr-2013 START
For Jdx = 0 To Idx - 1
.Selection.InsertFile FilesDir & FNArray(Jdx)
.Selection.TypeBackspace
.Selection.InsertBreak wdPageBreak
Next Jdx
' NEW 11-Apr-2013 END
.Selection.TypeBackspace
.Selection.TypeBackspace
.Selection.Document.SaveAs SingleFile
.Quit
End With
Set WordApp = Nothing
End Sub
' NEW 11-Apr-2013 START
Sub BubbleSort(Arr)
Dim strTemp As String
Dim Idx As Long, Jdx As Long
Dim VMin As Long, VMax As Long
VMin = LBound(Arr)
VMax = UBound(Arr)
For Idx = VMin To VMax - 1
For Jdx = Idx + 1 To VMax
If Arr(Idx) > Arr(Jdx) Then
strTemp = Arr(Idx)
Arr(Idx) = Arr(Jdx)
Arr(Jdx) = strTemp
End If
Next Jdx
Next Idx
End Sub
' NEW 11-Apr-2013 END
EDIT 11-Apr-2013
removed original comments in code
added array and bubblesort logic to guarantee files are retrieved in alphabetical order

Resources