Embed Excel VBA script in Powershell Script - vba

I wrote a Powershell script that queries all of my groups from Exchange Online, then gets membership for each of those groups and saves each group to a separate CSV file. This is all working fine.
(Some non relevant parts omitted for brevity):
$excelTemplate = "$($fileLocation)\group-breakout.xlsm"
Copy-Item $excelTemplate "$($tempPath)\group-breakout-$($curDate).xlsm"
# Creating Excel COM Object
$excel = new-object -comobject excel.application
$excelFile = Get-ChildItem -Path C:\temp\group-breakout -Include *.xlsm -Recurse
# Gathering all groups:
Write-Host "Getting all groups from Exchange Online..."
$allGroups = Get-DistributionGroup | select Name, PrimarySmtpAddress, ManagedBy, MemberJoinRestriction
$allGroups | Export-Csv "$($tempPath)\1-AllGroups.csv" -NoTypeInformation
Write-Host "`tDone."
# Gathering members for all groups
Write-Host "Getting members for all groups from Exchange Online..."
$allGroups | ForEach-Object {
Get-DistributionGroupMember -Identity $_.Name | select DisplayName, Alias, PrimarySmtpAddress | Export-Csv "$($tempPath)\$($_.name.Replace("/","-")).csv" -NoTypeInformation
}
Write-Host "`tDone."
# Calling macro in xlsm to merge the created csvs into a Excel spreadsheet
Write-Host "Creating Spreadsheet..."
$workbook = $excel.workbooks.open($excelFile) # Opening workbook
$worksheet = $workbook.worksheets.item(1) # Selecting worksheet
$excel.Run("ImportCSVs") # Calling macro
$workbook.save() #Saving workbook
$workbook.close() # Closing workbook
$excel.quit() # Closing Excel COM Object
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null # Making sure Excel process is closed.
I also have an Excel VBA script access through a COM object that will take those CSVs and add them to an XLSX with each CSV being one worksheet:
Sub ImportCSVs()
Dim fPath As String
Dim fCSV As String
Dim wbCSV As Workbook
Dim wbMST As Workbook
Set wbMST = ThisWorkbook
fPath = "C:\temp\group-breakout\" 'path to CSV files, include the final \
Application.ScreenUpdating = False 'speed up macro
Application.DisplayAlerts = False 'no error messages, take default answers
fCSV = Dir(fPath & "*.csv") 'start the CSV file listing
On Error Resume Next
Do While Len(fCSV) > 0
Set wbCSV = Workbooks.Open(fPath & fCSV) 'open a CSV file
wbMST.Sheets(ActiveSheet.Name).Delete 'delete sheet if it exists
ActiveSheet.Move After:=wbMST.Sheets(wbMST.Sheets.Count) 'move new sheet into Mstr
Columns.AutoFit 'clean up display
fCSV = Dir 'ready next CSV
Loop
Worksheets("1-AllGroups").Activate
Worksheets("placeholder").Delete
Workbooks.Application.ActiveWorkbook.SaveAs Filename:="C:\temp\group-breakout\group-breakout.xlsx", FileFormat:=51
Application.ScreenUpdating = True
Set wbCSV = Nothing
End Sub
The issue I have is having to include the Excel macro-enabled spreadsheet in the script location so I can access it from the script. Is there a way to somehow embed the VBA script in the Powershell script and run it inside of Excel? Or, alternatively, a way to perform the tasks in the VBA script using Powershell directly?

Related

VBA Copy and Pasting from one file to another without opening it

I am trying to link two files: one is the raw data file and the other is the master database with calculation that we maintain. We get raw data each new months, and we want to automate the process of saving the raw data into the database using a macro. However, I would like to link the two file without opening the raw data and running it into background.
I have tried the following:
Sub CopyDataNewMonth()
Dim filepath As String
Dim filemonth As String
Dim filename As String
Dim fileactive As String
Dim filemaster As String
Dim xlApp As New Excel.Application 'New Excel Application
Dim xlWB As Excel.Workbook 'Workbook Object
Dim ListSheet(1 To 15) As Variant
Dim i As Integer
ListSheet(1) = "Total Sales"
ListSheet(2) = "Total Volumes"
ListSheet(3) = "Hard Seltzer Sales"
ListSheet(4) = "Hard Seltzer Volumes"
xlApp.Visible = False 'Hide Application
filemaster = "Y:\Master Folder\Master.xlsm"
filepath = "Y:\Raw Data Folder\" 'the path of the file
Workbooks("Master.xlsm").Activate 'the folder where we save the file
Sheets("Control").Activate
filemonth = Range("B3").Value
For i = 1 To 4
Sheets(ListSheet(i)).Activate
filename = Range("B1").Value 'the name of the file
fileactive = filepath & filemonth & filename 'file path complete
Set xlWB = xlApp.Workbooks.Open(fileactive) 'Open File in new App
If ListSheet(i) = "Total Sales" Or ListSheet(i) = "Hard Seltzer Sales" Then
xlWB.Sheets("$").Range("A7:XCF1000").Copy
Workbooks("Master.xlsm").Activate
Sheets(ListSheet(i)).Activate
Range("B3").Select
ActiveSheet.Paste
Else
xlWB.Sheets("$").Range("A7:XCF1000").Copy
Workbooks("Master.xlsm").Activate
Sheets(ListSheet(i)).Activate
Range("B3").Select
ActiveSheet.Paste
End If
xlWB.Close
Next
End Sub
However, I am encountering some problems as Excel tells me that it is still waiting for OLE to conclude the task. I guess that once I open the file in background it stays open and does not close. Additionally, I would like that once the raw data file closes, I do not have to manually select Don't Save.
Thanks for your ideas!

VBA Issues with Inserting Multiple PDF Objects Within a Loop

My set-up is that I have a bunch of blank templates in a folder. Inside each blank template is a fund code (it is the only thing in the template)
The below macro I created (in an external workbook) goes through the folder with the templates, opens each template, and "fills it out" via a loop.
Basically my macro opens each template, assigns the fund code to a variable and then uses that variable in combination with some text strings to pull in other worksheets/PDF objects related to that specific fund code.
My issue is that in a more meaty version of the below code, I added maybe four or five more PDF objects to insert. It'll go through some of the templates and then randomly stop on a random fund code at a random pdf object insert line saying either "object cannot be found" or "object cannot be inserted"
If I press debug and then press F8 to run that line again, it is able to insert the object no problem. So perhaps my code is running too fast for adobe to handle? I am unsure. Perhaps my code isn't doing things as efficiently as possible. This would save sooo much time for my team, I just can't be having it work half the time.
(also the file names have definitely been correct, so that is not an issue)
Public Sub test()
Set currentbook = ActiveWorkbook
Application.AskToUpdateLinks = False
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Dim wbk As Workbook
Dim filename1 As String
Dim Path As String
Dim a As Long
Path = "C:\Users\Bob\Desktop\Workbooks\"
filename1 = Dir(Path & "*.xlsm")
'--------------------------------------------
'OPEN EXCEL FILES
Do While Len(filename1) > 0 'IF NEXT FILE EXISTS THEN
Set wbk = Workbooks.Open(Path & filename1)
wbk.Activate
'Gets Fund Code
Sheets("Initialize").Select
Dim FdCode As String
FdCode = Worksheets("Initialize").Range("D8")
'--------------------------- PDF ADDS
'Add PDF TB----------------------------------------------------
Worksheets("F.a - Working TB").OLEObjects.Add filename:="C:\Users\Bob\Desktop\Raw Reports\R122 04.30.16 - 04.30.17\" & FdCode & " 04.30.16 TB.PDF", Link:=False, DisplayAsIcon:=False, Left:=40, Top:=40, Width:=150, Height:=10
On Error GoTo 0
'Add PDF Closed Options----------------------------------------------------'
Worksheets("T300.1 - Options (Closed)").OLEObjects.Add filename:="C:\Users\Bob\Raw Reports\Other Reports 04.30.16-04.30.17\Breakout\" & FdCode & " other 04.30.17_ CLOSED OPTIONS POSITION REPORT.PDF", Link:=False, DisplayAsIcon:=False, Left:=40, Top:=40, Width:=150, Height:=10
On Error GoTo 0
ActiveWorkbook.Save
wbk.Close False
filename1 = Dir
Loop
Application.ScreenUpdating = True
End Sub

Merge many CSV files

I have set of 500 csv files. Each file has four columns and variable number of rows.
I want to merge all of these csv's into one common sheet. If someone can help me in doing this in PowerShell, it would be great.
Sample Data in Excel 1:
Name Age Marks Class
A 15 100 5
B 20 88 6
Sample Data in Excel 2:
Name Age Marks Class
C 11 99 2
Output :
Name Age Marks Class
A 15 100 5
B 20 88 6
C 11 99 2
If all the CSV files are in one folder then:
$res = #()
ls *.csv | %{
$temp = Import-CSV $_
$res += $temp
}
$res | Export-CSV .\ALLINFO.csv -NoTypeInformation
The break down:
$res = #() - Make an array called $res that will hold all the data. This isn't strictly required. You could do it in a way that appends to a result file directly.
ls *.csv | - Find all the CSV files in the folder and pass them to the next command.
%{$temp = Import-CSV $_; $res += $temp} - Take each of those files, import the CSV data into a holder variable called $temp. Add the contents of $temp to the collector variable $res. Again it is not necessary to use the intermediate $tamp variable, I just find it more clear to do so.
$res | Export-CSV .\ALLINFO.csv -NoTypeInformation - Now that the data from all the files is in $res, export $res to a new file.
If the files are large then you could merge them as text documents. This is a lot faster than importing csv-objects, but it requires that the properties and the order in which they're placed are equal in all files. Example:
$files = Get-ChildItem "*.csv"
#Get header
$text = #(Get-Content -Path $files[0].FullName -TotalCount 1)
$files | ForEach-Object {
#Get text but skip header
$text += Get-Content -Path $_.FullName | Select-Object -Skip 1
}
#Save merged csv
$text | Set-Content Output.csv
Output.csv
Name;Age;Marks;Class
A;15;100;5
B;20;88;6
C;11;99;2
You could optimize it even more by replacing Get-Content for [System.IO.File]::ReadAllLines() etc. but I skipped that now as it's more complicated/hard to read.
UPDATE: Added alternative solution that saves the output-file part for part as Ansgar suggested.
$outputfile = "Output.csv"
$files = Get-ChildItem "*.csv"
#Get header
Get-Content -Path $files[0].FullName -TotalCount 1 | Set-Content -Path $outputfile
$files | ForEach-Object {
#Get text but skip header
Get-Content -Path $_.FullName | Select-Object -Skip 1
} | Add-Content -Path $outputfile
In your case, the sort name is optional depending on whether the merge should also reorder the contents (obviously, you can sort on a different parameter as well). Same stipulation as above - all .csv files in one directory.
dir c:\directory_containing_your\*.csv | Import-Csv | sort name | Export-Csv -Path c:\output.csv -NoTypeInformation
From the ScriptingGuy.
Here's a heavily-commented solution that uses VBA in Excel to combine the CSVs. The strategy here is this:
Set your references up-front, most importantly the strDir variable (which is a string representing the directory that holds all your CSVs)
Loop through the directory
Open each CSV
Copy the appropriate contents from each CSV
Paste the contents to the output workbook
Repeat the loop until all files have been iterated over
Hope this helps!
Option Explicit
Public Sub CombineCSVsInFolder()
Dim strFile As String, strDir As String
Dim wbkSource As Workbook, wbkOutput As Workbook
Dim wksSource As Worksheet, wksOutput As Worksheet
Dim lngLastRowSource As Long, lngLastRowOutput As Long
Dim rngSource As Range, rngOutput As Range
Dim blnFirst As Boolean
'Set references up-front
strDir = "c:\stack\my_csvs\" '<~ edit this line with the CSV directory
strFile = Dir(strDir)
blnFirst = True
Set wbkOutput = Workbooks.Add
Set wksOutput = wbkOutput.ActiveSheet
Application.ScreenUpdating = False
'Loop through the CSV directory
While (strFile <> "")
'Assign source CSV files
Set wbkSource = Workbooks.Open(strDir & strFile)
Set wksSource = wbkSource.ActiveSheet
'Assign boundaries of area to copy and output
lngLastRowSource = LastRowNum(wksSource)
lngLastRowOutput = LastRowNum(wksOutput)
With wksOutput
Set rngOutput = .Cells(lngLastRowOutput + 1, 1)
End With
'If this is the first time through, include headers, otherwise do not
If blnFirst = False Then
With wksSource
Set rngSource = .Range(.Cells(2, 1), .Cells(lngLastRowSource, 4))
End With
'Special case for first iteration to correct source and output ranges
Else
With wksSource
Set rngSource = .Range(.Cells(1, 1), .Cells(lngLastRowSource, 4))
End With
With wksOutput
Set rngOutput = .Cells(1, 1)
End With
blnFirst = False
End If
'Execute copy, close source and repeat
rngSource.Copy rngOutput
wbkSource.Close
strFile = Dir
Wend
'Turn screen updates back on
Application.ScreenUpdating = True
End Sub
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT : Sheet, the worksheet we'll search to find the last row
'OUTPUT : Long, the last occupied row
'SPECIAL CASE: if Sheet is empty, return 1
Public Function LastRowNum(Sheet As Worksheet) As Long
If Application.WorksheetFunction.CountA(Sheet.Cells) <> 0 Then
LastRowNum = Sheet.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
Else
LastRowNum = 1
End If
End Function

Substitute Application.Getopenfilename with automation

I am looking to convert the code below to one where user is not required to select the files .
This code is to select a specific sheet from all the workbooks in the specific folder say" C:\Test"
This is a part of a consolidation macro
sub open_issues_sheet()
Dim Files as Variant
Files = Application.GetopenFilename("Excel FIles (*xl*),*xl*",Title:="Select Files", Multiselect:=True)
For Z = LBound(Files) To UBound(Files)
tem=Split(Files(Z),"\")
If(tem(UBound(tem)) <> ThisWorbook.Name) Then
Set S= Workbooks.Open(Files(Z))
S.Sheets("Issues").Select
'code to copy to current sheet
I tried using this http://spreadsheetpage.com/index.php/tip/getting_a_list_of_file_names_using_vba/
but I was getting a "Type Mismatch" Error at the Line "For Z =LBound"
Assuming that you still need the user to pick the folder containing the files that need to be consolidated, using the FileDialog(msoFileDialogFolderPicker) would be acceptable solution.
Dim sFilePath As String
With Application.FileDialog(msoFileDialogFolderPicker)
.AllowMultiSelect = False
.Title = "Select folder to consolidate"
If .Show = -1 Then
'Get the first file listed in the folder
sFilePath = Dir(.SelectedItems(1) & "\")
Do While Not sFilePath Like vbNullString
'Verify the extension before opening file
If Mid$(sFilePath, InStrRev(sFilePath, ".")) Like ".xls" Then
' Perform task ...
End If
'Get next file
sFilePath = Dir
Loop
End If
End With

VBScript - How do I get these workbooks to talk?

I posted earlier about getting my VBScript to wait until a process had finished before continuing (further info: VBScript - How to make program wait until process has finished?.
I was given an adequate answer after some discussion. However, it seems that I am now going in a new direction with the code as the solution presented another problem that I am hoping you may be able to help me with.
Basically I have some code which I have provided below. It takes in 4 arguments, one of which is a PATH to a folder containing many files which I want to use along with the other three in my VBA macro.
If WScript.Arguments.Count = 4 Then
' process input argument
Set args = WScript.Arguments
arg1 = args.Item(0)
arg2 = args.Item(1)
arg3 = args.Item(2)
arg4 = args.Item(3)
' Create a WshShell instance
Dim WShell
Set WShell = CreateObject("WScript.Shell")
' Create an Excel instance
Dim x1
Set x1 = CreateObject("Excel.Application")
' Disable Excel UI elements
x1.DisplayAlerts = False
x1.AskToUpdateLinks = False
'x1.AlertBeforeOverwriting = False
x1.FeatureInstall = msoFeatureInstallNone
' Open the Workbooks specified on the command-line
Dim x1WB
Dim x2WB
Dim x3WB
Dim x4WB
Dim strWB1
Dim strWB2
Dim strWB3
Dim strWB4
Dim FSO
Dim FLD
Dim FIL
Dim strFolder
strWB1 = arg1
Set x1WB = x1.Workbooks.Open(strWB1)
' Show the workbook/Excel program interface. Comment out for silent running.
x1WB.Application.Visible = True
strWB2 = arg2
Set x2WB = x1.Workbooks.Open(strWB2)
' Show the workbook/Excel program interface. Comment out for silent running.
x2WB.Application.Visible = True
strWB3 = arg3
Set x3WB = x1.Workbooks.Open(strWB3)
' Show the workbook/Excel program interface. Comment out for silent running.
x3WB.Application.Visible = True
'To hold the string of the PATH to the multiple files
strFolder = arg4
Set FSO = CreateObject("Scripting.FileSystemObject")
'Get a reference to the folder I want to search
set FLD = FSO.GetFolder(strFolder)
Dim strMyMacro
strMyMacro = "my_excel_sheet_with_vba_module.xlsm!Sheet1.my_vba_macro"
'loop through the folder and get the file names
For Each Fil In FLD.Files
WshShell.run """C:\Program Files\Microsoft Office\Office14\EXCEL.exe"" " & Fil, 1, true
x1.Run strMyMacro
'~~> Problem - How do I get the macro to run before opening the above file but run after it has opened (due to setting the bWaitOnReturn to true)
'~~> Problem - How do I get the file on current iteration to close after the macro has completed?
'~~> Problem - If this is not the issue, can you identify it?
Next
x1WB.close
x2WB.close
x3WB.close
'x4WB.close
' Clean up and shut down
Set x1WB = Nothing
Set x2WB = Nothing
Set x3WB = Nothing
Set x4WB = Nothing
Set FSO = Nothing
Set FLD = Nothing
x1.Quit
Set x1 = Nothing
Set WshShell = Nothing
WScript.Quit 0
Else
WScript.Quit 1
End If
The script works like this:
4 arguments are passed to the script. The 3rd argument is a .xlsm file which contains my VBA macro. The last argument is a PATH to a folder containing multiple files.
It then opens up the first three Excel files.
Then I run a loop to iterate through the files Fil in the folder that was specified as the 4th argument. AFAIK this has to be done via a WScript.shell using the .run method so that the rest of the script will hang until the Excel file it is processing finishes before closing it and opening up the next file in the folder.
After opening up file Fil, I then run the macro (albeit at this moment in time unsuccessfully).
I was tempted to simply open up all of the Excel files using the WScript.shell object however AFAIK I would not be able to run the macro this way.
Hopefully I have been able to define my aims of this piece of VBScript though if I haven't let me know and I shall clarify. Can you help?
Thanks,
QF.
Something along these lines might work for you (in Excel). A few things I'm not clear on though:
Where is your existing VBA macro - I'm guessing it's in one of the 3 files you're opening?
What types of files are in the folder you're looping through? I guessed Excel.
How is the vbscript being run? It looks like you're shelling out from your HTA, but why not include it directly in the HTA? That would save you from having to shell out and pass arguments...
Option Explicit
Dim wb1 As Workbook, wb2 As Workbook
Sub ProcessFolder(path1, path2, sFolder)
Dim wb As Workbook
Dim s
Set wb1 = Workbooks.Open(path1)
Set wb2 = Workbooks.Open(path2)
If Right(sFolder, 1) <> "\" Then sFolder = sFolder & "\"
s = Dir(sFolder & "*.xls*", vbNormal)
Do While Len(s) > 0
Set wb = Workbooks.Open(sFolder & s)
ProcessFile wb
wb.Close False
s = Dir()
Loop
wb1.Close False
wb2.Close False
End Sub
Sub YourExistingMacro(wb As Workbook)
'do stuff with wb and presumably the other 3 open files...
End Sub