I'm currently working on Powershell to handle excel files(*.xlsm).
The problem is the code below can only read "test.xlsm".
When the name is not test like "this.xlsm" , that code cannot read the file.
Any help...?
Thanks for your answer in advance :)
$destination = "C:\JJ\"
$dirName = Get-ChildItem -Name -Filter *.xlsm
$saveAs = $destination + "new\"
foreach($z in $dirName){
$excel=New-Object -ComObject Excel.Application
$excel.visible=$false
$excel.Displ`ayAlerts=$false
$book=$excel.Workbooks.Open($destination + $z)
$sheet=$book.Worksheets.item(1)
$sheet.Cells.Item(1,5)="=max(B2:B6)"
$book.SaveAs($saveAs + $z)
$excel.Quit()
$excel=$null
}
You are using confusing variable names in your code.. Why call the source path $destination??
Anyway, you should use the -File switch on your Get-ChildItem cmdlet to make sure this will only return FileInfo objects, not DirectoryInfo objects aswell. (these are Objects, not strings)
Then, there is a better way to construct paths. Use Join-Path instead of concatenating things like with $destination + $z.
Lastly, I would create the Excel object only once, before the loop and cleanup memory afterwards. Now, you are creating new COM objects in every iteration and never release them from memory.
Below code should do what you intend:
$source = "C:\JJ"
$destination = Join-Path -Path $source -ChildPath 'new'
# test if the destination path already exists and if not, create it
if (!(Test-Path -Path $destination -PathType Container)) {
$null = New-Item -Path $destination -ItemType Directory
}
# create the Excel COM object outside the loop
$excel = New-Object -ComObject Excel.Application
$excel.visible = $false
$excel.DisplayAlerts = $false
# get all *.xlsm files inside the source folder
Get-ChildItem -Path $source -Filter '*.xlsm' -File | ForEach-Object {
# The $_ automatic variable represents 1 FileInfo object in each iteration
$book = $excel.Workbooks.Open($_.FullName)
$sheet = $book.Worksheets.item(1)
$sheet.Cells.Item(1,5) = "=max(B2:B6)"
# join the destination path and the file name for output
$saveAs = Join-Path -Path $destination -ChildPath $_.Name
$book.SaveAs($saveAs)
$book.Close()
}
# cleanup Com objects
$excel.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($book)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
$excel = $null
Note, If you have PowerShell version below 3.0, the -File switch is not available. Instead, then use
Get-ChildItem -Path $source -Filter '*.xlsm' | Where-Object {!$_.PSIsContainer} | ForEach-Object { ... }
# remove -name to get a directory object instead of a string
$dirName = Get-ChildItem -Filter *.xlsm
# you need the file name from the directory object listing
foreach($z in $dirName.name){ ...... }
Related
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.
I'm trying to create an Excel workbook, then populate the cells with data found from searching many txt files.
I read a file and extract all comments AFTER I find "IDENTIFICATION DIVISION" and BEFORE I find "ENVIRONMENT DIVISION"
I then populate two cells in my excel workbook. cell one if the file and cell two is the comments extracted.
I have 256GB of memory on the work server. less than %5 is being used before Powershell throws the memory error.
Can anyone see where I'm going wrong?
Thanks,
-Ron
$excel = New-Object -ComObject excel.application
$excel.visible = $False
$workbook = $excel.Workbooks.Add()
$diskSpacewksht= $workbook.Worksheets.Item(1)
$diskSpacewksht.Name = "XXXXX_Desc"
$col1=1
$diskSpacewksht.Cells.Item(1,1) = 'Program'
$diskSpacewksht.Cells.Item(1,2) = 'Description'
$CBLFileList = Get-ChildItem -Path 'C:\XXXXX\XXXXX' -Filter '*.cbl' -File -Recurse
$Flowerbox = #()
ForEach($CBLFile in $CBLFileList) {
$treat = $false
Write-Host "Processing ... $CBLFile" -foregroundcolor green
Get-content -Path $CBLFile.FullName |
ForEach-Object {
if ($_ -match 'IDENTIFICATION DIVISION') {
# Write-Host "Match IDENTIFICATION DIVISION" -foregroundcolor green
$treat = $true
}
if ($_ -match 'ENVIRONMENT DIVISION') {
# Write-Host "Match ENVIRONMENT DIVISION" -foregroundcolor green
$col1++
$diskSpacewksht.Cells.Item($col1,1) = $CBLFile.Name
$diskSpacewksht.Cells.Item($col1,2) = [String]$Flowerbox
$Flowerbox = #()
continue
}
if ($treat) {
if ($_ -match '\*(.{62})') {
Foreach-Object {$Flowerbox += $matches[1] + "`r`n"}
$treat = $false
}
}
}
}
$excel.DisplayAlerts = 'False'
$ext=".xlsx"
$path="C:\Desc.txt"
$workbook.SaveAs($path)
$workbook.Close
$excel.DisplayAlerts = 'False'
$excel.Quit()
Not knowing what the contents of the .CBL files could be, I would suggest not to try and do all of this using an Excel COM object, but create a CSV file instead to make things a lot easier.
When finished, you can simply open that csv file in Excel.
# create a List object to collect the 'flowerbox' strings in
$Flowerbox = [System.Collections.Generic.List[string]]::new()
$treat = $false
# get a list of the .cbl files and loop through. Collect all output in variable $result
$CBLFileList = Get-ChildItem -Path 'C:\XXXXX\XXXXX' -Filter '*.cbl' -File -Recurse
$result = foreach ($CBLFile in $CBLFileList) {
Write-Host "Processing ... $($CBLFile.FullName)" -ForegroundColor Green
# using switch -File is an extremely fast way of testing a file line by line.
# instead of '-Regex' you can also do '-WildCard', but then add asterikses around the strings
switch -Regex -File $CBLFile.FullName {
'IDENTIFICATION DIVISION' {
# start collecting Flowerbox lines from here
$treat = $true
}
'ENVIRONMENT DIVISION' {
# stop colecting Flowerbox lines and output what we already have
# output an object with the two properties you need
[PsCustomObject]#{
Program = $CBLFile.Name # or $CBLFile.FullName
Description = $Flowerbox -join [environment]::NewLine
}
$Flowerbox.Clear() # empty the list for the next run
$treat = $false
}
default {
# as I have no idea what these lines may look like, I have to
# assume your regex '\*(.{62})' is correct..
if ($treat -and ($_ -match '\*(.{62})')) {
$Flowerbox.Add($Matches[1])
}
}
}
}
# now you have everything in an array of PSObjects so you can save that as Csv
$result | Export-Csv -Path 'C:\Desc.csv' -UseCulture -NoTypeInformation
Parameter -UseCulture ensures you can double-click the file so it will open correctly in your Excel
You can also create an Excel file from this csv programmatically like:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open('C:\Desc.csv')
$worksheet = $workbook.Worksheets.Item(1)
$worksheet.Name = "XXXXX_Desc"
# save as .xlsx
# 51 ==> [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
# see: https://learn.microsoft.com/en-us/office/vba/api/excel.xlfileformat
$workbook.SaveAs('C:\Desc.xlsx', 51)
# quit Excel and remove all used COM objects from memory
$excel.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($worksheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
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:\
I am taking an excel report moving it from a share drive with a partial file name changing it to an txt file then renaming it than removing the one from citrix. Problem is the file is not changing to a txt or renaming and also not removing itself from citrix
I am very new to powershell could anybody offer insight?
Move-Item -Path 'S:\test\test*.xls' -destination c:\test
$files = Get-ChildItem c:\test\test*.xls
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Excel.DisplayAlerts = $false
ForEach ($file in $files) {
Write "Loading File '$($file.Name)'..."
$WorkBook = $Excel.Workbooks.Open($file.Fullname)
$NewFilePath = [System.IO.Path]::ChangeExtension($file.Fullname,".txt")
$Workbook.SaveAs($NewFilepath, 42) # xlUnicodeText
}
# cleanup
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($WorkBook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Rename-Item -Path "C:\test\test*.txt" -NewName "test1.txt"
Remove-Item -Path C:\test\test*.txt `
I used below code to convert file from CSV to xlsx. But it only convert single file at a time. I want this to convert all the files in directory at a time.
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $true
$Workbook = $xl.Workbooks.Open("$loglocation\errors_$server.csv")
$Worksheets = $Workbooks.Worksheets
$Workbook.SaveAs("$loglocation\errors_$server.xls",1)
$Workbook.Saved = $true
$xl.Quit()
With the PSExcel Module you can use Export-XLSX which makes this process very simple:
$loglocation = "C:\folder"
Get-ChildItem -Path $loglocation -Filter *.csv | foreach {
Export-XLSX -InputObject $_ -Path "$loglocation\$($_.BaseName).xlsx"
}
Try this, should work:
$filePath = Get-ChildItem -Path "path to csv" -filter *.csv
foreach ($file in $filePath )
{
$filename = $file.FullName
$filename
$xl = new-object -comobject excel.application
$xl.visible = $true
$Workbook = $xl.workbooks.open($filename)
$Worksheets = $Workbooks.worksheets
$Workbook.SaveAs($filename.Substring(0,$filename.Length-4) + ".xlsx",1)
$Workbook.Saved = $True
$xl.Quit()
}
Can move the excel connections outside the loop as well if you need to speed it up