What is the reason for Powershell Excel SaveAs / Exception null-valued expression? - excel

I'm working on a PowerShell script which unzips some .csv files, writes the contents inside an existing .xlsx file and saves this as a new .xlsx.
After some calculation I start a batch file from my PL/SQL code with DBMS.SCHEDULE. After the .bat file did some preparation it starts the PowerShell script.
I'm getting no error if I run the .bat file manually in a Windows command prompt window.
But I get an error while saving the .xlsx file if it is started from PL/SQL.
System.Management.Automation.RuntimeException You cannot call a method on a null-valued expression.
I tried to search in world wide web for a solution on this error but couldn't find one.
This is the line which produces the error message:
$WB.SaveAs($fileName, 51, [Type]::Missing, [Type]::Missing, $false, $false, 1, 2) | Out-Null
Here is the whole code:
#Unzip Files
function Unzip($file, $destination){
Expand-Archive $file -Destination $destination -Force
if (Test-Path $file) {
Remove-Item $file
}
}
#Add file to existing zip
function addZip($file, $destination){
Compress-Archive -Path $file -Update -DestinationPath $destination
}
#Write CSV into XLSX
function WriteFile($row, $column, $range){
foreach ($content in Get-Content $f.FullName) { #Get row
#If value not empty
if ($content) {
#Set value to cell
$WS.Cells.Item($row, $column) = $content
$colA = $ws.Range("$range$row")
$colrange = $ws.Range("$range$row")
#Split value by delimiter
#https://learn.microsoft.com/en-us/office/vba/api/excel.range.texttocolumns
$colA.TextToColumns($colrange, 1, -4142, $false, $false, $false, $false, $false, $true, "|") | Out-Null
$row = $row + 1
}
}
}
#Start
$XL = New-Object -ComObject Excel.Application
#Disable alerts
$XL.EnableEvents = $false
$XL.DisplayAlerts = $false
$XL.ScreenUpdating = $false
$param = $args[0].SubString(0, $args[0].IndexOf(".")) #Get name
$currentPath = Get-Location
$newPath = "$currentPath\$param\" #Temp new folder
#Create temp folder if exists
if (!(Test-Path $newPath)) {
New-Item -ItemType Directory -Force -Path $newPath
}
#Unzip
Unzip "$currentPath\$param.zip" $newPath
$files = Get-ChildItem -Path $newPath -Recurse #Get files
$XL.Workbooks.Open("$currentPath\test.xlsx")
$WB = $xl.ActiveWorkbook
$fileName = "$newPath"+"test_$param.xlsx"
#Loop through files
foreach ($f in $files) {
if ($f.Basename.ToUpper() -like "*A") {
$WS = $WB.Sheets.Item("A")
$WS.Activate() | Out-Null
WriteFile 10 1 "A" #Row / Column
addZip $f.FullName "$currentPath\$param.zip"
} elseif ($f.Basename.ToUpper() -like "*B") {
$WS = $WB.Sheets.Item("B")
$WS.Activate() | Out-Null
WriteFile 11 1 "A" #Row / Column
addZip $f.FullName "$currentPath\$param.zip"
} elseif ($f.Basename.ToUpper() -like "*C") {
$WS = $WB.Sheets.Item("C")
$WS.Activate() | Out-Null
WriteFile 12 1 "A" #Row / Column
addZip $f.FullName "$currentPath\$param.zip"
}
}
try {
#Delete File if exists
if ((Test-Path $fileName)) {
Remove-Item -Path $fileName -Recurse -Force -Confirm:$false
}
#Save
$WB.SaveAs($fileName, 51, [Type]::Missing, [Type]::Missing, $false, $false, 1, 2) | Out-Null
} catch {
New-Item "$currentPath\log.txt" -ItemType file
$_.Exception.GetType().FullName, $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber |
Out-File "$currentPath\log.txt"
} finally {
#Close, Quit
$WB.Close($false) | Out-Null
$XL.Workbooks.Close()
$XL.Quit() | Out-Null
}
try {
addZip $fileName "$currentPath\$param.zip"
} catch {
New-Item "$currentPath\log1.txt" -ItemType file
$_.Exception.GetType().FullName, $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber |
Out-File "$currentPath\log1.txt"
}
#Delete folder
if ((Test-Path $newPath)) {
Remove-Item -Path $newPath -Recurse -Force -Confirm:$false | Out-Null
}
#Release ComObject
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($XL)
spps -n Excel
This is the .bat file which runs the Powershell script:
powershell.exe -ExecutionPolicy RemoteSigned -File C:\Scripts\write_to_excel.ps1 %1
This is the PL/SQL code which runs the .bat file:
BEGIN
DBMS_SCHEDULER.create_job ('SCHEDULE_POWERSHELL',
job_action => 'c:\windows\system32\cmd.exe',
number_of_arguments => 4,
job_type => 'executable',
enabled => FALSE);
DBMS_SCHEDULER.set_job_argument_value ('SCHEDULE_POWERSHELL', 1, '/q');
DBMS_SCHEDULER.set_job_argument_value ('SCHEDULE_POWERSHELL', 2, '/c');
DBMS_SCHEDULER.set_job_argument_value ('SCHEDULE_POWERSHELL', 3, 'c:\Scripts\run_powershell.bat');
DBMS_SCHEDULER.set_job_argument_value ('SCHEDULE_POWERSHELL', 4, 'test_files.zip');
DBMS_SCHEDULER.enable ('SCHEDULE_POWERSHELL');
DBMS_SCHEDULER.run_job (job_name => 'SCHEDULE_POWERSHELL', use_current_session => TRUE);
EXCEPTION
WHEN OTHERS
THEN
log_trace_message ('SCHEDULE_POWERSHELL: ' || SQLERRM);
log_trace_message ('SCHEDULE_POWERSHELL: ' || DBMS_UTILITY.format_error_backtrace);
END;

Related

PowerShell for Excel: Sever external connections / remove queries / unlink external table data

I have a script which copies CSVs for each sheet in a formatted ("staging") workbook. The workbook is externally connected to a data source: a CSV export from our webform solution. Once this is completed, the script copies the staging workbook as well. But, in this new directory, the instance which is to display a specific form submission is still externally connected, so it's presently a pointless backup because it refreshes to the current form submissions export each time it opens.
TL;DR
How do I sever external connections / remove queries / unlink external table data in PowerShell?
Thanks in advance! And for the record, there are several business limitations so unless it's a mild adjustment to the current workflow, I won't be able to fulfill suggestions, i.e.- API, querying WP DB, Ansible, etc.
`
#set-executionpolicy -executionpolicy bypass
$Hosts = ".\ESXiHosts.csv"
$vCenter = ".\vCenter.csv"
$vSwitchPortGroups = ".\vSwitchPortGroups.csv"
$source = ".\source.csv"
$pivot = ".\pivot.csv"
If ((test-path $Hosts) -eq $true) {
Remove-Item $Hosts}
If ((test-path $vCenter) -eq $true) {
Remove-Item $vCenter}
If ((test-path $vSwitchPortGroups) -eq $true) {
Remove-Item $vSwitchPortGroups}
If ((test-path $vSwitchPortGroups) -eq $true) {
Remove-Item $source}
If ((test-path $vSwitchPortGroups) -eq $true) {
Remove-Item $pivot}
$staging = "formidableforms_staging"
$staging_file = $staging + ".xlsx"
$xlsxLoc = "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$staging_wb = $Excel.Workbooks.Open($xlsxLoc+$staging_file)
$staging_wb.refreshall()
$connections = $staging_wb.Connections
while($connections | ForEach-Object {if($_.OLEDBConnection.Refreshing){$true}}){
Start-Sleep -Seconds 1
}
$staging_wb.Save()
$Excel.Quit()
Function ExportWSToCSV ($excelFileName, $csvLoc)
{
$excelFile = $excelFileName + ".xlsx"
$E = New-Object -ComObject Excel.Application
$E.Visible = $false
$E.DisplayAlerts = $false
$wb = $E.Workbooks.Open($csvLoc+$excelFile)
foreach ($ws in $wb.Worksheets)
{
$n = $ws.Name
$ws.SaveAs($csvLoc + $n + ".csv", 6)
}
$E.Quit()
}
ExportWSToCSV -excelFileName "formidableforms_staging" -csvLoc "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\"
stop-process -processname EXCEL
#Remove-Item -path "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\pivot.csv"
#Remove-Item -path "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\source.csv"
$CleanupHosts = Import-CSV $Hosts
$CleanupHosts | Where-Object {$_.ESXiHostDNSName -ne ''} | Export-CSV -path ".\ESXiHosts.csv" -NoTypeInformation -Force
$CleanupvCenter = Import-CSV $vCenter
$CleanupvCenter | Where-Object {$_.vCenterFQDN -ne ''} | Export-CSV -path ".\vCenter.csv" -NoTypeInformation -Force
$CleanupvSwitchPortGroups = Import-CSV $vSwitchPortGroups
$CleanupvSwitchPortGroups | Where-Object {$_.Type -ne ''} | Export-CSV -path ".\vSwitchPortGroups.csv" -NoTypeInformation -Force
$createdir = Import-CSV $vCenter
$custfolder = ($createdir).FOLDER
$FOLDERdate = (Get-Date -Format "MM-dd-yyyy_HHmm")
Try {new-item -path . -name $custfolder -itemtype "directory" -ErrorAction Stop}
Catch {write-host "The folder: $custfolder already exists."}
new-item -path $custfolder\ -name $FOLDERdate -itemtype "directory"
Copy-item *.CSV -Destination .\$custfolder\$FOLDERdate
Remove-item .\ESXiHosts.csv
Remove-item .\vCenter.csv
Remove-item .\vSwitchPortGroups.csv
Remove-item .\pivot.csv
Remove-item .\source.csv
Remove-item .\Hosts.csv
Remove-item .\Network.csv
Remove-item .\Storage.csv
Remove-item .\Other.csv
cd "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\"
Copy-item *.XLSX -Destination .\$custfolder\$FOLDERdate\$custfolder.xlsx
cd .\$custfolder\$FOLDERdate
Remove-item .\pivot.csv
cd "\\blahblah\fs\groups\SysOps\Managed Infrastructure\Client Intake\Formidable Forms\"
`
I cannot find documentation for any of the requested items, only adjusting the existing properties of the connection, or GUI how-tos.

Powershell script to search through a directory of excel files to find a string only searching through 1 file

I have found this script on https://shuaiber.medium.com/
I want to use it to find a certain string in a folder full of excel files.
the problem I am encountering is that it basically only searches through 1 file and then stops...
here is the script
Function Search-Excel {
[cmdletbinding()]
Param (
[parameter(Mandatory, ValueFromPipeline)]
[ValidateScript({
Try {
If (Test-Path -Path $_) {$True}
Else {Throw "$($_) is not a valid path!"}
}
Catch {
Throw $_
}
})]
[string]$Source,
[parameter(Mandatory)]
[string]$SearchText
#You can specify wildcard characters (*, ?)
)
$Excel = New-Object -ComObject Excel.Application
Try {
$Source = Convert-Path $Source
}
Catch {
Write-Warning "Unable locate full path of $($Source)"
BREAK
}
$Workbook = $Excel.Workbooks.Open($Source)
ForEach ($Worksheet in #($Workbook.Sheets)) {
$Found = $WorkSheet.Cells.Find($SearchText) #What
If ($Found) {
$BeginAddress = $Found.Address(0,0,1,1)
#Initial Found Cell
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $BeginAddress
}
Do {
$Found = $WorkSheet.Cells.FindNext($Found)
$Address = $Found.Address(0,0,1,1)
If ($Address -eq $BeginAddress) {
BREAK
}
[pscustomobject]#{
WorkSheet = $Worksheet.Name
Column = $Found.Column
Row =$Found.Row
Text = $Found.Text
Address = $Address
}
} Until ($False)
}
Else {
Write-Warning "[$($WorkSheet.Name)] Nothing Found!"
}
}
$workbook.close($false)
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$excel)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable excel -ErrorAction SilentlyContinue
}
And then I would use
Get-ChildItem -Path "C:\excelfiles" -Recurse -Include *.xls, *.xlsx, *.xlsm | Select-Object -Property Directory, Name | ForEach-Object { "{0}{1}" -f $.Directory, $.Name } | Search-Excel -SearchText MyText
I know its only searching through 1 file because I looked at another file and tried to get it to send me back to confirm yet it doesnt work.
Any help would be greatly appreciated.
You're going to need to include a loop within your function, or put the function within a ForEach-Object loop. For the function change you could do:
Function Search-Excel {
[cmdletbinding()]
Param (
[parameter(Mandatory, ValueFromPipeline)]
[ValidateScript({
Try {
If (Test-Path -Path $_) {$True}
Else {Throw "$($_) is not a valid path!"}
}
Catch {
Throw $_
}
})]
[string]$Source,
[parameter(Mandatory)]
[string]$SearchText
#You can specify wildcard characters (*, ?)
)
$Excel = New-Object -ComObject Excel.Application
ForEach($Path in $Source){
### <the rest of your existing code here, adjusted to work with $Path instead of $Source>
}
[void][System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$excel)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable excel -ErrorAction SilentlyContinue
}
Or, to work with your existing function you could just do:
Get-ChildItem -Path "C:\excelfiles" -Recurse -Include *.xls, *.xlsx, *.xlsm |
ForEach-Object { Search-Excel -Source $_.FullName -SearchText MyText }
So let's talk about a couple of things with your command.
Get-ChildItem -Path "C:\excelfiles" -Recurse -Include *.xls, *.xlsx, *.xlsm | Select-Object -Property Directory, Name | ForEach-Object { "{0}{1}" -f $.Directory, $.Name } | Search-Excel -SearchText MyText
If we break this up into the individual pieces (separated by pipes), we might be able to figure out what's wrong. The first part, Get-ChildItem, is only looking for files matching *.xls, *.xlsx, and *.xlsm, so already directories are excluded. Well, what exactly does this function return? If you were to look at the object types, you'll see System.IO.FileInfo, which has a bunch of properties built in. One of which is the full file path needed, .FullName.
Currently, Search-Excel is setup to only search one file. If you want to search multiple files at once, you'll need a loop somewhere. In my opinion, the easiest place to do that will be outside of the function, like this:
Get-ChildItem -Path "C:\excelfiles" -Recurse -Include *.xls, *.xlsx, *.xlsm | Foreach-Object { Search-Excel -Source $_.FullName -SearchText MyText }

Powershell Unable to find Excel and open Excel File

I am trying to have Powershell copy, rename than edit a excel file. It copies and renames the file as intended however when I go to open the file with excel it is unable to find the file. See attached code.
Thank you for the help.
#Export Textbox outputs
$S0 = $textBox1.Text
$jobname = $textBox2.Text
$contractor = $TextBox3.Text
#combine textbox outputs
$folder = "$S0" + "_" + "$jobname" + "_" + "$contractor"
$subsubfolder = ".\"+"$folder" + "\Dir"
$takeoffname = "$s0" + "_takeoff.xlsx"
#Excel
$xl = New-Object -ComObject excel.application
Start-Sleep -Seconds 5
$xl.Visible = $true
Start-Sleep -Seconds 5
$wb = $xl.Workbooks.Open("$subsubfolder\$takeoffname")
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = "$jobname"
$data.Cells.Item(1,7) = "$S0"
$wb.Save()
$xl.Quit()
NEW updated Code - Added Join path and It broke the create folder loop. Sorry IF the added requirement to make the folder creates extra problems.
$S0 = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
$folder = ' {0}_{1}_{2}' -f $S0, $jobname, $Contractor
$file = '{0}_takeoff.xlsx' -f $S0
$PILname = 'PIL_{0}.xlsx' -f $S0
Write-host $folder
New-Item -ItemType Directory "./$folder"
foreach($line in Get-Content $Filenames)
{
New-Item $folder\$line -ItemType Directory
}
$subfolder = '{0}\1 - Estimating Original Quote Material' -f $folder
$subsubfolder = Join-Path -Path $PWD - ChildPath $Subfolder
$filePath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $subsubfolderfolder -ChildPath $file)
$PILpath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $subsubfolderfolder -ChildPath $PILname)
Write-host $filePath
Write-host $subsubfolder
pause
#Copy Files
Copy-Item '.\_master_takeoff.xlsx' "$subsubfolder\_master_takeoff.xlsx"
Copy-Item '.\PIL_S0XXXXX .xlsx' $subsubfolder
#Rename Files
Rename-Item -Path "$subsubfolder\_master_takeoff.xlsx" -newname $takeoffname
Rename-Item -Path "$subsubfolder\PIL_S0XXXXX .xlsx" -newname $PILpath
$xl = New-Object -ComObject excel.application
Start-Sleep -Seconds 5
$xl.Visible = $true
Start-Sleep -Seconds 5
$wb = $xl.Workbooks.Open("$subsubfolder\$takeoffname")
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = "$jobname"
$data.Cells.Item(1,7) = "$S0"
$wb.Save()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
By taking strings off textboxes and combining that to a file path with string concatenation, you're bound to end up with a path that doesn't exist.
Having said that, the error comes from using the .\ in the path.
Powershell may know where that is, but Excel will have no idea where to look for the file. Excel has its own Default path, usually pointing to the Documents folder and when given relative paths, it will use that.
Always use existing, absolute file paths for opening stuff in external applications.
Better use something like this
#Export Textbox outputs
$prefix = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
#combine textbox outputs to form the directory (I like using the -f format operator)
$file = '{0}_takeoff.xlsx' -f $prefix
$folder = '{0}_(1}_{2}\Dir' -f $prefix, $jobname, $contractor
$filePath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $folder -ChildPath $file)
# test if the file can be founc
if (Test-Path $filePath -PathType Leaf) {
$xl = New-Object -ComObject excel.application
$xl.Visible = $true
$wb = $xl.Workbooks.Open($filePath)
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = $jobname
$data.Cells.Item(1,7) = $prefix
$wb.Save()
$xl.Quit()
# important: clean-up COM objects after use
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
else {
Write-Warning "File '$filePath' not found"
}
Instead of using $PWD (Print Working Directory) you can also use Get-Location which in fact is the same thing
Since I have no idea why your updated code is creating subfolders, I'll leave that out here.
Please look at how the -f Format operator works because now you're doing that wrong.
Also, to not confuse the working directory for PowerShell and the default path for Excel anymore, define the full root path first in the code. Below I'm using a variable called $workingDir for that.
Copy-Item can copy and rename at the same time.
# let's forget about the 'Set-Location' and use absolute paths from the beginning
$workingDir = '\\Server\Share\Folder' # set this to the real path
# Export Textbox outputs
$S0 = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
# combine textbox outputs to form the directory (I like using the -f format operator)
$PILname = 'PIL_{0}.xlsx' -f $S0
$file = '{0}_takeoff.xlsx' -f $S0
$folder = '{0}_(1}_{2}\1 - Estimating Original Quote Material' -f $S0, $jobname, $contractor
$folderPath = Join-Path -Path $workingDir -ChildPath $folder # --> Full absolute path to the working folder
$filePath = Join-Path -Path $folderPath -ChildPath $file # --> Full absolute path to the file
Write-host $filePath
Write-host $folderPath
#Copy and rename master Files
$masterFile = Join-Path -Path $workingDir -ChildPath '_master_takeoff.xlsx'
$pilFile = Join-Path -Path $workingDir -ChildPath 'PIL_S0XXXXX.xlsx'
Copy-Item -Path $masterFile -Destination $filePath
Copy-Item -Path $pilFile -Destination (Join-Path -Path $folderPath -ChildPath $PILname)
############################
#Write to new take off file
############################
# Call excel and open file
$xl = New-Object -ComObject excel.application
$xl.Visible = $true
$wb = $xl.Workbooks.Open($filePath)
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = $jobname
$data.Cells.Item(1,7) = $S0
$wb.Save()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Everything seems ok with the code, but you are using relative paths.
If you are doing that, you need to change the working directory before opening the excel.
Ex: Set-Location C:\

Multiple csv files into a xlsx file but different sheets using powershell

I have 20 csv files. Each are unrelated. How do I combine them together into one xlsx file with 20 sheets, each named after the csv files.
$root = "C:\Users\abc\Desktop\testcsv"
$CSVfiles = Get-ChildItem -Path $root -Filter *.csv
$xlsx = "C:\Users\abc\Desktop\testxl.xlsx" #output location
$delimiter = "," #delimiter
#Create a excel
$xl=New-Object -ComObject Excel.Application
$xl.Visible=$true
#add a workbook
$wb=$xl.WorkBooks.add(1)
ForEach ($csv in $CSVfiles){
#name the worksheet
$ws=$wb.WorkSheets.item(1)
$ws.Name = [io.path]::GetFileNameWithoutExtension($csv)
$TxtConnector = ("TEXT;" + $csv)
$Connector = $ws.QueryTables.add($TxtConnector,$ws.Range("A1"))
$query = $ws.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $delimiter
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,1 * $ws.Cells.Columns.Count
$query.AdjustColumnWidth = 1
# Execute & delete the import query
$query.Refresh()
$query.Delete()
$wb.SaveAs($xlsx,51)
}
# Save & close the Workbook as XLSX.
$xl.Quit()
This way, change the first line to the folder where you store those 20 CSV files and then
$path="c:\path\to\folder" #target folder
cd $path;
$csvs = Get-ChildItem .\* -Include *.csv
$y=$csvs.Count
Write-Host "Detected the following CSV files: ($y)"
foreach ($csv in $csvs)
{
Write-Host " "$csv.Name
}
$outputfilename = $(get-date -f yyyyMMdd) + "_" + $env:USERNAME + "_combined-data.xlsx" #creates file name with date/username
Write-Host Creating: $outputfilename
$excelapp = new-object -comobject Excel.Application
$excelapp.sheetsInNewWorkbook = $csvs.Count
$xlsx = $excelapp.Workbooks.Add()
$sheet=1
foreach ($csv in $csvs)
{
$row=1
$column=1
$worksheet = $xlsx.Worksheets.Item($sheet)
$worksheet.Name = $csv.Name
$file = (Get-Content $csv)
foreach($line in $file)
{
$linecontents=$line -split ',(?!\s*\w+")'
foreach($cell in $linecontents)
{
$worksheet.Cells.Item($row,$column) = $cell
$column++
}
$column=1
$row++
}
$sheet++
}
$output = $path + "\" + $outputfilename
$xlsx.SaveAs($output)
$excelapp.quit()
cd \ #returns to drive root
https://stackoverflow.com/a/51094040/5995160 answer is too slow when dealing with csv's with a ton of data, I modified this solution to use https://github.com/dfinke/ImportExcel. This has greatly improved the performance of this task, at least for me.
Install-Module ImportExcel -scope CurrentUser
$csvs = Get-ChildItem .\* -Include *.csv
$csvCount = $csvs.Count
Write-Host "Detected the following CSV files: ($csvCount)"
foreach ($csv in $csvs) {
Write-Host " -"$csv.Name
}
$excelFileName = $(get-date -f yyyyMMdd) + "_" + $env:USERNAME + "_combined-data.xlsx"
Write-Host "Creating: $excelFileName"
foreach ($csv in $csvs) {
$csvPath = ".\" + $csv.Name
$worksheetName = $csv.Name.Replace(".csv","")
Write-Host " - Adding $worksheetName to $excelFileName"
Import-Csv -Path $csvPath | Export-Excel -Path $excelFileName -WorkSheetname $worksheetName
}
This solution assumes that the user has already changed directories to where all the csv's live.
See below for a solution with uses the OpenText method.
At least two things to note:
I'm assuming your workbook creates a single sheet by default. if creates more than that, you will need to modify the script so that these additional sheets are deleted from the end result.
The way you specify TextFileColumnDataTypes is quite clever. You will need to modify it and feed the array to the FieldInfo argument below. See the documentation linked above for the kind of array it is expecting.
$CSVfiles = Get-ChildItem -Path $root -Filter *.csv
$xlsx = "C:\Users\abc\Desktop\testxl.xlsx" #output location
#Create a excel
$xl = New-Object -ComObject Excel.Application
$xl.Visible=$true
#add a workbook
$wb = $xl.WorkBooks.add(1)
# how many worksheets do you have in your original workbook? Assuming one:
$ws = $wb.Worksheets.Item(1)
ForEach ($csv in $CSVfiles){
# OpenText method does not work well with csv files
Copy-Item -Path $csv.FullName -Destination ($csv.FullName).Replace(".csv",".txt") -Force
# Use OpenText method. FieldInfo will need to be amended to suit your needs
$xl.WorkBooks.OpenText(`
($file.FullName).Replace(".csv",".txt"), # Filename
2, # Origin
1, # StartRow
1, # DataType
1, # TextQualifier
$false, # ConsecutiveDelimiter
$false, # Tab
$false, # Semicolon
$true, # Comma
$false, # Space
$false, # Other
$false, # OtherChar
#() # FieldInfo
)
$tempBook = $xl.ActiveWorkbook
$tempBook.worksheets.Item(1).Range("A1").Select() | Out-Null
$tempBook.worksheets.Item(1).Move($wb.Worksheets.Item(1)) | Out-Null
# name the worksheet
$xl.ActiveSheet.Name = $csv.BaseName
Remove-Item -Path ($csv.FullName).Replace(".csv",".txt") -Force
}
$ws.Delete()
# Save & close the Workbook as XLSX.
$wb.SaveAs($xlsx,51)
$wb.Close()
$xl.Quit()

PowerShell Scripts to Look for Excel files with .xls and .xlsx extensions

I have an already written scripts which looks for ONLY an excel file with .xls extension. Now,
I want the same scripts to look for either .xls or .xlsx which ever it finds in that particular location and use it......
The old scripts search for files in the F column only on the excel sheet/file.....This makes the search takes forever, so I would like it to search the files in the F column which ONLY have this path:root_project/Fut_DB_Projects in the E column of the excel sheet.
Pls let me know exactly where to insert it in the scripts. I'm very new to Powershell and your answers would be very much appreciated.
# Creating an object for the Excel COM addin
$excelfile = $args[0]
$folder = $args[1]
echo $excelfile
echo $folder
if($excelfile -ne $Null -and $excelfile.Contains(".xls") -and (Test-Path $excelfile) -eq $True)
{
if($folder -ne $Null -And $folder.Contains("\") -and (Test-Path $folder) -eq $True)
{
$ExcelObject = New-Object -ComObject Excel.Application
# Opening the Workbook
$ExcelWorkbook = $ExcelObject.Workbooks.Open($excelfile)
# Opening the Worksheet by using the index (1 for the first worksheet)
$ExcelWorksheet = $ExcelWorkbook.Worksheets.Item(1)
#The folder where the files will be copied/The folder which will be zipped later
$a = Get-Date
$targetfolder = "C:\"+$a.Month+"_"+$a.Day+"_"+$a.Year+"_"+$a.Hour+$a.Minute+$a.Second
#Check if the folder already exists. Command Test-Path $targetfolder returns true or false.
if(Test-Path $targetfolder)
{
#delete the folder if it already exists. The following command deletes a particular directory
Remove-Item $targetfolder -Force -Recurse -ErrorAction SilentlyContinue
}
#The following command is used to create a particular directory
New-Item -ItemType directory -Path $targetfolder
echo "Temp folder created"
#Declaration of variables, COlumn value = 6 for Column F
$row = 1
$col = 6
# Read a value from the worksheet with the following command
$filename = $ExcelWorksheet.Cells.Item($row,$col).Value2
$filename
#change the folder value below to specify the folder where the powershell needs to search for the filename that it reads from excel file.
$null = ""
# Loop through each row in the excel file.
do
{
$filename = $ExcelWorksheet.Cells.Item($row,$col).Value2
#Checking if value read from Excel is Null
#In powershell operator for NOT EQUAL TO is -ne not <>
if($filename -ne $Null)
{
$filepath = $folder+$filename
#Check if the filepath read from excel is a real filepath OR check if the file exists.
if(Test-Path $filepath)
{
#If the file exists, move it to the folder declared above
# Change the below command to Move-Item if you want to Move the file and not Copy...
Copy-Item $filepath $targetfolder
}
else
{
#$Allfiles = Get-ChildItem -Recurse $folder
#You add the folders to the following list that you want the script to skip as it searches
#for the files through directory and its subdirectories
#eg. $xdir = #("Folder1","Folder2","Folder3")
$xdir = #("Tables","Views","Update","Synonyms","BES","ADTLTransit","Fut_DB_Jobs","Triggers","Scripts")
$remove = [string]::join("|",$xdir)
$Allfiles = Get-ChildItem -Recurse $folder | ? { $_.DirectoryName -notmatch $remove}
Write-Host -Fore Yellow ("Looking through subfolders now")
foreach ($file in $Allfiles)
{
$testpath = $file.FullName
if($testpath.Contains($filename))
{
Write-Host -Fore Yellow ("Found the file in a subfolder at location:" + $testpath)
Move-Item $testpath $targetfolder
}
}
}
}
#incrementing the row variable by 1 to move to the next available row in excel.
$row = $row + 1
}
#while condition evaluates if value read from the excel is null. If null, then the loop breaks.
while($filename -ne $Null)
# Important: The object needs to quit and the variables release, otherwise
# an Excel.exe will remain open.
$ExcelObject.Quit()
$ExcelObject = $null
$ExcelWorkbook = $null
$ExcelWorksheet = $null
[GC]::Collect()
do
{
}
while((Test-Path -path $targetfolder) -ne $True)
$targetfolderinfo = Get-ChildItem -Recurse $targetfolder | Measure-Object
$targetfolderfilecount = $targetfolderinfo.count
if($targetfolderfilecount -ne 0)
{
#Following command calls the function to zip all the files moved from source folder to target folder
$directory = [IO.DirectoryInfo] $targetfolder
If ($directory -eq $null)
{
Throw "Value cannot be null: directory"
}
Write-Host ("Creating zip file for folder (" + $directory.FullName + ")...")
[IO.DirectoryInfo] $parentDir = $directory.Parent
[string] $zipFileName
If ($parentDir.FullName.EndsWith("\") -eq $true)
{
# e.g. $parentDir = "C:\"
$zipFileName = $parentDir.FullName + $directory.Name + ".zip"
}
Else
{
$zipFileName = $parentDir.FullName + "\" + $directory.Name + ".zip"
}
If (Test-Path $zipFileName)
{
Remove-Item $zipFileName -Force -Recurse -ErrorAction SilentlyContinue
}
Set-Content $zipFileName ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
$shellApp = New-Object -ComObject Shell.Application
$zipFile = $shellApp.NameSpace($zipFileName)
If ($zipFile -eq $null)
{
Throw "Failed to get zip file object."
}
[int] $expectedCount = (Get-ChildItem $directory -Force -Recurse).Count
$expectedCount += 1 # account for the top-level folder
$zipFile.CopyHere($directory.FullName)
Write-Host -Fore Green ("Successfully created zip file for folder (" `
+ $directory.FullName + ").")
}
else
{
Write-Host -Fore Red ("There are no files to be zipped")
}
[System.Threading.Thread]::Sleep(10000)
Remove-Item $targetfolder -recurse
}
else
{
Write-Host -Fore Red ("Target folder path not specified correctly")
}
}
else
{
Write-Host -Fore Red ("Excel file path not specified")
}
Try changing the first if to this
if($excelfile -ne $Null -and $excelfile -like "*.xls*" -and (Test-Path $excelfile) -eq $True)

Resources