I have a very weird problem.
I have some vba scripts called merge1-6 which extracts data from different excel sheets.
These 6 resulting text files are merged using another macro "MergeALL" into 1 text file, so everything is on one string.
So far so good, it works fine.
So I wanted to make a powershell script that access the current excel document i have open, and fetch the J29 cell data, and merge this data with the value "0|" to combine a given string.
The actual powershell script I have to pull the cell data is as follows:
Macro : Call BID_MergeXCleanup_TextOut
#This script opens H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt and replace empty parts of "domain/users/0|" with "nothing"
#stripping away all entries not needed. Out-File used as output, with -NoNewline to prevent carriage return inserting empty line.
#$workbook = $excel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm") #open workbook
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject(Excel.Application')
$workbook = $xl.workbooks | ?{$_.FullName -eq "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm"}
$workbook.sheets.item(1).activate() #opens the sheet and activates it
$WorkbookTotal=$workbook.Worksheets.item(1) #Set sheet data to variable
$value = $WorkbookTotal.Cells.Item(29, 10) # Fetches data in Column 10, Row 29 which is the cell reference J29 where domain is.
$domain = $value.Text #Fetches "domain/users/" from the Fetch-tab of the excel file, to match the correct domain in use and stores result in the $domain variable
$leftoverdomain = '0|' #last part of domain string after the domain/user part
$joinedvariables = -join($domain,$leftoverdomain,"") #Joines the above variables together to form "domain/users/0|" as searchtag to replace as leftover bits.
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" #Opens text file to replace in.
#Next part uses the $joinedvariables variable of merged search parametres and replaces it with "nothing" and saves the file back.
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
### INFO ###
#I used a reference to the EXCEL cell here, instead of directly from the TXT-file as given what domain has been specified, the info in the TEXT file would vary
#But text in J29 would always equal the domain being used, and thus also correnspond with the one in the text file at all times.
#This way it will automatically always use the correct domain.
So, if I use a standalone reference to the excel document,
$workbook = $excel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm")
...and directly open the workbook, in powershell ISE, the command is successful. BUT does not execute correctly if I call the PS1 script or run it manually from within the excel file. Which is why I started looking for how to access the current excel session leading me to the next method...
If I use
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
to reference the current running application, it successful from both ISE and inside Excel, when I run manually.
In both instances this work, as long as the TXT file that is to be modified exist and has the information in it that is searched for.
So given that I have an "unmodified file" I wanted to launch these macros by calling them from another macro.
This is the VBA code used from within the same excel sheet to launch the macros.
Call BID_1_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_2_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_3_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_4_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_5_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_6_TextOut
Application.Wait Now + TimeSerial(0, 0, 1)
Call BID_MergeALL_TextOut 'Macro MergeALL - Merges all BID-TEXTOUT files to 1 file
Application.Wait Now + TimeSerial(0, 0, 3)
Call BID_MergeXCleanup_TextOut 'Macro MergeXCleanup - This script replace empty parts of "domain/users/0|" with "nothing"
Application.Wait Now + TimeSerial(0, 0, 1)
The two last macros are the important ones.
2nd last macro prepare the file that is to be used in "BID_MergeXCleanup" which is the PS1 script I am calling.
The macros i call are with the following code:
' Script to merge all the BID-TEXT-OUT files into 1 string with UTF32LE encoding.
Sub BID_MergeALL_TextOut()
Set objShell = CreateObject("wscript.Shell")
objShell.Run ("powershell.exe H:\Temp\PublicFolder-powershell\MergeALL_UTF32.ps1")
End Sub
and
'This script opens H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt and replace empty parts of "domain/users/0|" with "nothing"
'stripping away all entries not needed.
Sub BID_MergeXCleanup_TextOut()
Set objShell = CreateObject("wscript.Shell")
objShell.Run ("powershell.exe -noexit H:\Temp\PublicFolder-powershell\REPLACE_IN_EmptyDomainUsersPart.ps1")
End Sub
The weird thing is, if I use the "calling script" to run these commands in succession automatically, the last script ALWAYS fails
If I inspect the TXT file afterwards, I see that only the '0|' variable was removed, as it seems it was not able to fetch the J29 information from the Excel document.
If I run each of the macros 1 by 1 from within Excel after each other manually then they execute correctly and the scripts do as I want giving no errors
merge1-6-then-all-then-cleanup
So from what I can tell, my powershell script is able to call the active excel workbook, sheet, cell, get data and all that.
...but I just cant do it with calling the macro from another macro? In this example it fails if I use the "Call BID_MergeXCleanup_TextOut" reference to access the macro.
PS: I also tried to "share" the workbook, in case it was a permission issue, but it still gave error when running the Call-VBA script, but not manually running each macro in succession.
Any idea how I can fix this?
I have a feeling i might not allow the original PS1 file to have a $NULL expression exit, but i am unsure how to add it in.
The first code line that fails is
$workbook.sheets.item(1).activate()
I tried to add $NULL to it in Powershell ISE and then it fails
$null = $workbook.sheets.item(1).activate() <<<--- Fails with *"You cannot call a method on a null-valued expression."*
And i cannot see that i have any undecleared variables in the main script either.
Unsure how to figure this one out.
What i ended up with:
So #postanote up here suggested i Use COMs instead of InteropService.Marshal to access Excel.
I initially thought this would not work, since it would open a new instance of the workbook, but the COMs version have a way of hiding the instance you run with
$objExcel.Visible = $false
Now, the [Runtime.InteropServices.Marshal]::GetActiveObject version also have this value, it seems to be different somehow because, if I change my code to be COMs as follows:
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open("H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm")
$WorkSheet = $WorkBook.sheets.item("Fetch")
$domain = $worksheet.Rows.Item(29).Columns.Item(10).Text
$leftoverdomain = '0|' #last part of domain string after the domain/user part
$joinedvariables = -join($domain,$leftoverdomain,"") #Joines the above variables together to form "domain/users/0|" as searchtag to replace as leftover bits.
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" #Opens text file to replace in.
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
...The script excecute without error when called from the VBA call script...
If I on the other hand try to incorporate the non-visible member in the original code as here:
$xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application') #opens an already open excel session
$xl.Visible = $false
$workbook = $xl.workbooks | ?{$_.FullName -eq "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.xlsm"}
$workbook.sheets.item(1).activate()
$WorkbookTotal=$workbook.Worksheets.item(1)
$value = $WorkbookTotal.Cells.Item(29, 10)
$domain = $value.Text
$leftoverdomain = '0|'
$joinedvariables = -join($domain,$leftoverdomain,"")
$b = Get-Content -Path "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt"
#(ForEach ($a in $b) {$a.Replace($joinedvariables, '')}) | Out-File "H:\Temp\PublicFolder-powershell\Create-String-For-Email-Fetch.txt" -NoNewline
Start-Sleep -Seconds 0.3
...then it fails with the following changed error message:
Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))
So i guess the end result is that COMs makes it possible to open the same document more than once with the same name, as long as you hide the new excel instance from Windows window handler.
But...it would have been nice to know why the original code worked when ran manually, but not when called from the script, so if anyone knows, I'd love to know the answer to that one.
Related
I am trying to save a specific Excel Sheet from a Macro Enabled Excel Workbook (xlsm) via Powershell to csv to upload it into a database. This is done via Powershell since it needs to be automated along with some more data processing etc.
The Situation:
I have a list of excel files in a directory, each having the same structure/sheets.
I parse each file to a Powershell function which creates a new Excel Object and opens the workbook.
In the next step I am trying to save a specific sheet (here Sheet with Index 2)
The Problem:
Iterating through each Worksheet and saving them gives me all Sheets including the one I am looking for (Sheet 2)
Accessing Sheets 2 via $ws = $wb.Worksheets(2) also gives me the right Sheet (according $ws.name) but saving $ws via $ws.SaveAs("$destinationDirectory" + $File.BaseName + ".csv", 6) results in a csv file containing Sheet 1.
I have saved Worksheets with basically exactly the same code successfully before (except instead of xlsm I was dealing with xlsx).
Code
Function ExcelToCsv ($File) {
echo "Converting $($File.Name) to csv..."
$Excel = New-Object -ComObject Excel.Application;
$Excel.DisplayAlerts = $False;
$wb = $Excel.Workbooks.Open($File)
$ws = $wb.Worksheets(2)
echo "ws is:" + $ws.name # Correctly printing Worksheet name of Sheet 1
$ws.SaveAs("$destinationDirectory" + $File.BaseName + ".csv", 6) # Saving Sheet 1 instead of Sheet 2
$wb.Close($True);
}
}
foreach ($file in $files){
ExcelToCsv -File $file;
}
Workaround
My current workaround is to iterate through the sheets via foreach
$n = 1
foreach($ws in $wb.Worksheets){
$ws.SaveAs("$destinationDirectory" + $File.BaseName + "-$($n).csv", 6)
$n = $n+1
}
And deleting any unwanted sheets (recognized by $n != 2) via
Remove-Item "$($destinationDirectory)*-[13456789].csv";
Which works but is not really optimal.
Also: checking for $n -eq 2 in the foreach and only saving that sheet also does not work since it will simply save sheet 1 again.
And ideas are greatly appreciated!
After a frustrating afternoon I eventually worked this one out - you need to call Activate against the sheet you want to save e.g.
$sheet = $book.sheets.item('User_Specified_Report')
$Sheet.Activate()
$sheet.SaveAs($newName,6)
I've written a Powershell script that converts every worksheet within an Excel-workbook to a seperate PDF-file. The code below works properly when the output PDF-file fits within one page.
When the content of the worksheet is large and should normally be saved to two pages, it attempts to fit it on one page. This results in a cell overlapping the footer. It should be on the next page.
The error message shows an error in the ExportAsFixedFormat method. This exception only shows when the output PDF should be exported across two or more pages.
Value does not fall within the expected range. (System.ArgumentException)
In an attempt to debug this, I came across the PageSetup property. I wanted to see the amount of pages that it contained so I added Write-Host $Worksheet.PageSetup.Pages to the inner loop.
Oddly, adding this Write-Host solved the issue. The content now spreads itself across multiple pages. Even weirder, while the PDF-files get created correctly, the error message is still there. I have two questions:
How did adding a Write-Host statement fix the export?
How do I get rid of the error message (without just supressing it)?
I've also played around with properties like PageSetup.FitToPagesTall (set to 2) and PageSetup.FitToPagesWide (set to 1), but this messed up the layout of the PDF-file.
$Formats = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$ExcelFiles = Get-ChildItem $ExcelDirectory -Filter "*.xlsx" -Recurse
foreach($ExcelFile in $ExcelFiles)
{
$Workbook = $Excel.Workbooks.Open($ExcelFile.FullName)
$Workbook.Saved = $true
foreach($Worksheet in $Workbook.Worksheets)
{
if($Worksheet.Name -eq "Voorblad")
{
continue
}
# Some algorithm to calculate the output file name.
# Not relevant for this question. Results in the variable $SheetPrettyName
$OutputPath = "$ExcelDirectory\" + $SheetPrettyName + ".pdf"
$Worksheet.ExportAsFixedFormat($Formats::xlTypePDF, $OutputPath)
}
$Excel.Workbooks.Close()
}
$Excel.Quit()
So, getting the error message five times and the export having to split the PDF at five points was not related. The Excel-file has been created through a number of templates, which reside in the workbook as invisible worksheets. Coincidentally, there were five hidden worksheets.
The program attempted to export and save these worksheets. But logically, failed to do so.
I've added a check to my loop which checks if the worksheet is hidden or very hidden. These visibility options are represented by a 1 or 2 respectively (-1 for visible).
if(($Worksheet.Name -eq "Voorblad") -or ($Worksheet.Visible -ne -1))
{
continue
}
I was also able to remove the Write-Host I added earlier, the script remained functional.
I have been at this for a while and can't seem to find anything that does exactly what I want. I was working off this post: How to use PowerShell to copy several Excel worksheets and make a new one but it doesn't do exactly what I am looking for. I am attempting to copy all worksheets from multiple workbooks and place them in a new workbook. Ideally, I would like to get the file name from one of the workbooks and use it to name the new file in a new directory.
I have found numerous examples of code that can do this, but there are a couple of key features that I am missing and not sure how to implement them. Here is an example of what I have working now:
$file1 = 'C:\Users\Desktop\TestFolder\PredictedAttritionReport.xlsx' # source's fullpath
$file2 = 'C:\Users\Desktop\TestFolder\AdvisorReport' # destination's fullpath
$xl = new-object -c excel.application
$xl.displayAlerts = $false # don't prompt the user
$wb2 = $xl.workbooks.open($file1, $null, $true) # open source, readonly
$wb1 = $xl.workbooks.open($file2) # open target
$sh1_wb1 = $wb1.sheets.item('Report') # second sheet in destination workbook
$sheetToCopy = $wb2.sheets.item('Report') # source sheet to copy
$sh1_wb1.Name = "DeleteMe$(get-date -Format hhmmss)" #Extremely unlikely to be a duplicate name
$sheetToCopy.copy($sh1_wb1) # copy source sheet to destination workbook
$sh1_wb1.Delete()
$wb2.close($false) # close source workbook w/o saving
$wb1.close($true) # close and save destination workbook
$xl.quit()
spps -n excel
The problem that I have is that I need this to work, such that I don't have to input the actual file name since those names may be different each time they are created and there may be 3 or 4 files with more than one worksheet, where this example works off of only two named files. Additionally, I would like to be able to copy all worksheets in a file instead of just a single named worksheet, which in this case is 'Report'. The final piece is saving it as a new Excel file rather than overwriting the existing destination file, but I think I can figure that part out.
You could specify parameters when you call the script, and use the specified values in place of the sheet/file names as required. Read about PowerShell Parameters here. Reply if you need more info on how to implement.
This function will copy over sheets from one excel workbook to other.
You can call with Copy-AllExcelSheet command one, it is loaded into memory.
See below in example:
Function Copy-AllExcelSheet()
{
param($TargetXls, $sourceXls)
$xl = new-object -c excel.application
$xl.displayAlerts = $false
$sourceWb = $xl.Workbooks.Open($sourceXls,$true,$false)
$targetWB = $xl.Workbooks.Open($TargetXls)
foreach($nextSheet in $sourceWb.Sheets)
{
$nextSheet.Copy($targetWB.Sheets[1])
}
$sourceWb.close($false)
$targetWB.close($true)
$xl.Quit()
}
#To call the function
Copy-AllExcelSheet "c:\Targetfile.xlsx" "c:\sourceFile.xlsx"
I am attempting to automate the process of adding a worksheet (with data) per clientname in excel for a monthly report type workbook
I thought it should be straight forward... but the method I am using isnt working.... it doesn't even get to the sheet making mode... can you help me figure out what I did wrong?
The following is the function I made
function Excelclientstatstemplate ($clients) {
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
#### Check if Clients worksheet exists, if no then make one with client name ###
$sheetcheck = if (($clientws)) {} Else {
$WS = $WB.worksheets.add
$WS.name = "$clients"
}
$sheetcheck
$WB.Save
# Enter stat labels
$clientws.cells.item(1,1) = "CPU Count"
$clientws.cells.item(2,1) = "RAM"
$clientws.cells.item(3,1) = "Reserved CPU"
$clientws.cells.item(4,1) = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2) = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2) = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2) = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2) = [decimal]$cstats.resmemoryLimitGB
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
and then I tried to make a Foreach thing for it
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
The entire Process of the function goes
Take client name
Open a particular excel workbook
Check if there are any sheets with client name
If there are NO sheets with client name, make one with client name
Fill The first column Cells with labels
Fill the second column cells with data (data works I already write CSVs withem)
Save and exit
The Foreach variable just does the function for each of Clients names from a clientlist (nothing wrong with clientlist)
Am I messing something up?
Thanks for the help.
You are not calling the .Add() method correctly. You are missing the parenthesis at the end of it. To fix it you should be able to simply modify the line to this:
$WS = $WB.worksheets.add()
Also, the cells have properties that you should refer to, so I would also modify the part that sets your cell values to something like this:
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
I'm fairly sure that defining the type is pointless, since to Excel they're all strings until you set the cell's formatting settings to something else. I could be wrong, but that is the behavior that I have observed.
Now, for other critiques that you didn't ask for... Don't launch Excel, open the book, save the book, and close Excel for each client. Open Excel once at the beginning, open the book, make your updates for each client, and then save, and close.
Test to see if the client has a sheet, and add it if needed, then select the client's sheet afterwords. Right now there's nothing there to set $clientws if you have to add one for that client.
Adding a worksheet by default places it before the active worksheet. This was a poor choice in design in my opinion, but it is what it is. If it were me I'd add new sheets specifying the last worksheet in the workbook, which will add the new worksheet before the last one, making it the second to the last worksheet. Then I'd move the last worksheet up in front of the new one, effectively adding the new worksheet as the last one listed. Is it possible to add the new worksheet as the last one when you make it? Yes, but it's was too complicated for my taste. See here if you are interested in doing that.
When testing for an existing client worksheet to make one if it is missing, do that, don't tell it to test for something, and do nothing, and put everything you want in an Else statement. That just complicates things. All that said, here's some of those suggestions put into practice:
function Excelclientstatstemplate ($clients) {
#### Check if Clients worksheet exists, if no then make one with client name ###
if (($clients -notin $($WB.worksheets).Name)){
#Find the current last sheet
$LastSheet = $WB.Worksheets|Select -Last 1
#Make a new sheet before the current last sheet so it's near the end
$WS = $WB.worksheets.add($LastSheet)
#Name it
$WS.name = "$clients"
#Move the last sheet up one spot, making the new sheet the new effective last sheet
$LastSheet.Move($WS)
}
#Find the current client sheet regardless of if it existed before or not
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL
I have a script that will create worksheets based on the number of files that it finds in a directory. From there it changes the name of the sheets to the file name. During that process, I am attempting to add two Column header values of "Hostname" and "IP Address" to every sheet. I can achieve this by activating each sheet individually but this becomes rather cumbersome as the amount of sheets goes past 20+ and thus I am trying to find a dynamic way of doing this regardless the amount of sheets that are present.
This is the code that I have to do everything up to the column header portion:
$WorksheetCount = (Get-ChildItem $Path\Info*.txt).count
$TabNames = Get-ChildItem $Path\Info*.txt
$NewTabNames = Foreach ($file IN $TabNames.Name){$file.Substring(0,$file.Length-4)}
$Break = 0
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$Workbook = $Excel.Workbooks.Add()
$null = $Excel.Worksheets.Add($MissingType, $Excel.Worksheets.Item($Excel.Worksheets.Count),
$WorksheetCount - $Excel.Worksheets.Count, $Excel.Worksheets.Item(1).Type)
1..$WorksheetCount
Start-Sleep -s 1
ForEach ($Name In $NewTabNames){
$Break++
$Excel.Worksheets.Item($Break).Name = $Name
}
I have attempted to insert my code as such:
ForEach ($Name In $NewTabNames){
$Break++
$Excel.Worksheets.Item($Break).Name = $Name
$cells=$Name.Cells
$cells.item(1,1)="Hostname"
$cells.item(1,2)="IP Address"
}
When I attempt to run the script, I get the following error..
You cannot call a method on a null-valued expression.
And then it proceeds to list each line of the code that I had put in. I thought that since I created a variable during the operation, that it was the issue:
$cells=$Name.Cells
I thought That perhaps if I moved it before the ForEach command that it would resolve it but I still receive the same issue. I have looked through various ways of trying to select ranges of sheets within excel via powershell but have not found anything helpful.
Would appreciate any assistance on this.
This is actually my first post in StackOverflow ever and I feel pretty excited to finally help out. I made some small modifications to your code and seems to work fine. I noticed some odd behavior when I removed the $null variable that was getting assigned because it seemed strange to me why it was being done, but after removing that assignment my outlook application open by itself automatically every time I ran the script. I found the site where you got the code from just to see if there were any changes to the original code.
I found this Microsoft documentation very helpful to figure this out.
This is what I modified
ForEach ($Name In $NewTabNames){
$Break++
$Excel.Worksheets($Break).Name = $Name
$Excel.Worksheets($Break).Cells(1,1).Font.Bold = $true
$Excel.Worksheets($Break).Cells(1,1) = "Hostname"
$Excel.Worksheets($Break).Cells(1,2).Font.Bold = $true
$Excel.Worksheets($Break).Cells(1,2) = "IP Address"
}