Why does PowerShell not want to save my file as a CSV - excel

$Path = 'D:/ETL_Data/TwitchTVData.xlsx'
$csvPath = 'D:/ETL_Data/TwitchTVData2.csv'
# Open the Excel document and pull in the 'Sheet1' worksheet
$Excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($Path)
$page = 'Sheet1'
$ws = $Workbook.Worksheets | Where-Object {$_.Name -eq $page}
$Excel.Visible = $true
$Excel.DisplayAlerts = $false
# Set variables for the worksheet cells, and for navigation
$cells = $ws.Cells
$row = 1
$col = 4
$formula = #"
=NOW()
"#
# Add the formula to the worksheet
$range = $ws.UsedRange
$rows = $range.Rows.Count
for ($i=0; $i -ne $rows; $i++) {
$cells.Item($row, $col) = $formula
$row++
}
$ws.Columns.Item("A:D").EntireColumn.AutoFit() | Out-Null
$ws.Columns.Range("D1:D$rows").NumberFormat = "yyyy-MM-dd hh:mm"
$Excel.ActiveWorkbook.SaveAs($csvPath)
$Excel.Quit()
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
https://www.experts-exchange.com/questions/27996530/How-to-convert-and-xlsx-spreadsheet-into-CSV.html#answer38780402-20
I was attempting to follow that, but for some reason for me the SaveAs() doesn't work. It gives me an error
cannot access the file 'D://ETL_Data/E567DF00
What do I have to do to get this to save over to CSV?
Edit:
Exact error without the fileformat parameter 6 as suggested in the comments:
Microsoft Excel cannot access the file 'D:\//ETL_Data/8011FF00'. There are
several possible reasons:
o The file name or path does not exist.
o The file is being used by another program.
o The workbook you are trying to save has the same name as a currently open
workbook.
At D:\PS_Scripts\twitchExcelAddSnapShot.ps1:32 char:1
+ $Excel.ActiveWorkbook.SaveAs($csvPath)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
Exact error with fileformat parameter 6:
The file could not be accessed. Try one of the following:
o Make sure the specified folder exists.
o Make sure the folder that contains the file is not read-only.
o Make sure the file name does not contain any of the following characters:
< > ? [ ] : | or *
o Make sure the file/path name doesn't contain more than 218 characters.
At D:\PS_Scripts\twitchExcelAddSnapShot.ps1:32 char:1
+ $Excel.ActiveWorkbook.SaveAs($csvPath,6)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException

While PowerShell is pretty forgiving when it comes to path separators, COM+ servers (like Excel.Application) might not be.
Change the $csvPath variable value to use \ instead of /:
$csvPath = 'D:\ETL_Data\TwitchTVData2.csv'

To complement Mathias R. Jessen's helpful answer with background information:
It seems that Excel's application-specific behavior is the cause of your problem, unrelated to the underlying foundational subsystems or APIs used.
(Excel's automation API happens to be a COM server.)
I'm unclear on why Excel acts this way - it also does so in interactive use, though you could argue that at least in programmatic use it should allow / too.
To offer generalized advice:
On Windows, to be safe, use \, especially when dealing with application-level automation APIs, though at the level of system APIs / should work as well - see below.
In cross-platform code, use / (but watch out for the exceptions above; [System.IO.Path]::DirectorySeparatorChar reports the platform-appropriate (primary) character).
Though rarely used, Windows at the API level allows interchangeable use of \ and / (which apparently goes back to the DOS 2.0 days), when support for directories was introduced), and that is also reflected in higher-level subsystems, as the following examples demonstrate.
# PS: OK
# Should output c:\windows\win.ini
(Get-Item c:/windows/win.ini).FullName
# .NET: OK
# Should return the 1st child dir.'s path.
# Note that the child directory names will be appended with "\", not "/"
[System.IO.Directory]::GetDirectories('c:/windows/system32') | Select-Object -First 1
# COM (see Excel exception below): OK
# Should return $true
(New-Object -ComObject Scripting.FileSystemObject).FileExists('c:/windows/win.ini')
# Windows API: OK
# Should return a value such as 32.
(Add-Type -PassThru WinApiHelper -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint GetFileAttributes(string lpFileName);')::GetFileAttributes('c:/windows/win.ini')
# cmd.exe: INCONSISTENTLY SUPPORTED
# Note: *quoting matters* here, so that tokens with / aren't mistaken for options.
# attrib: works
cmd /c 'attrib "c:/windows/win.ini"'
# dir: works with DIRECTORIES, but fails with FILES
cmd /c 'dir /b "c:/windows/system32"' # OK
cmd /c 'dir /b "c:/windows/win.ini"' # FAILS with 'File not found'
cmd /c 'dir /b "c:/windows\win.ini"' # Using \ for the FILE component (only) works.
Here's a minimal example that demonstrates Excel's problem with /:
# Create temporary dir.
$null = mkdir c:\tmp -force
$xl=New-Object -Com Excel.Application
$wb = $xl.Workbooks.Add()
# OK - with "\"
$wb.SaveAs('c:\tmp\t1.xlsx')
# FAILS - with "/":
# "Microsoft Excel cannot access the file 'C:\//tmp/F5D39400'"
$wb.SaveAs('c:/tmp/t2.xlsx')
$xl.Quit()
# Clean up temp. dir.

Related

How to change delimiter in excel CSV saving using powershell

Using Powershell and Excel 2016, I'm trying to open a .xlsx file, extract a single page, and save this page as a .csv with a " ; " delimiter. The problem is that while Excel expects " ; " delimiter when opening a csv file, it always saves them with a " , " delimiter.
I'd prefer to not have to change any settings, this is a script i'm writing for a project that needs to work natively on any pc, so having to go and change settings every time I need it to run on another computer would be problematic.
I already checked that the list delimiter settigs in windows was indeed a " ; ", and it is.
I tried every type of CSV saving described in the microsoft doc (https://learn.microsoft.com/fr-fr/office/vba/api/excel.xlfileformat),
what's weird is that when saving a file from the GUI version, I only have 3 versions of CSV, instead of 5 listed on the website, and one of them is "CSV with " ; " delimiter", which works as intended, but I can't seem to use this type of file when saving using Excel via Powershell
There's apparently a "local" flag that can be activated for Excel to use the delimiter settings of windows, but I have no idea of how ot activate it in Powershell and I'd prefer not to use this since it means that the program wouldn't work on a Windows with a different delimiter configuration.
# Args[0] : file to open
# [1] : file to save
# page_to_extract : name of the page I need
# I open an Excel session
$excel_session = New-Object -Com Excel.Application
$excel_session.displayAlerts = $false
# I open the file I need to extract the page from
$excel_workbook = $excel_session.workbooks.open($args[0])
# I load in the page
$excel_worksheet = $excel_workbook.worksheets($page_to_extract)
# I save the page using a csv type (6,22,24,62,23)
$excel_worksheet.saveAs($args[1], 6)
$excel_session.quit()
This code always saves my csv with a " , " delimiter, I need " ; " instead.
I need to use Powershell and ONLY Powershell for this, no windows settings, no excel settings.
I had success with the following code with my own data. This uses your COM Object assignment code. I added logic to extract the cells that contain data, add that data to a new custom object on each row iteration, store each custom object in an array, and finally pipe the array into Export-Csv. Your specified delimiter ; is used in the Export-Csv command.
$excel_session = New-Object -Com Excel.Application
$excel_session.displayAlerts = $false
# I open the file I need to extract the page from
$excel_workbook = $excel_session.workbooks.open($args[0])
# I load in the page
$excel_worksheet = $excel_workbook.worksheets($page_to_extract)
# Get Range of Used Cells in Worksheet
$range = $excel_worksheet.usedrange
# Get First Row Column Text to be Used as Object Properties
$headers = $range.rows.item(1).value2
# Loop through Rows and Columns to Extract Data
# First loop traverses rows
# Second loop traverses columns
$output = for ($i = 2; $i -le $range.rows.count; $i++) {
$hash = [ordered]#{}
for ($j = 1; $j -le $range.columns.count; $j++) {
[void]$hash.Add($headers.GetValue(1,$j),$range.rows.item($i).columns.item($j).Text)
}
[pscustomobject]$hash
}
$output | Export-Csv file.csv -NoType -Delimiter ';'
# Clean Up COM Objects
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel_workbook)
[void][System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel_session)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
An inefficient, but simple and pragmatic workaround is to:
Use your code as-is to let Excel temporarily produce an interim ,-separated CSV file.
Import that file with Import-Csv (which uses , by default), and export again with Export-Csv -Delimiter ';'.
In the context of your code:
(Import-Csv $args[1]) | Export-Csv $args[1] -Delimiter ';' -NoTypeInformation
Note:
The Import-Csv call is enclosed in (...) to ensure that the input file is read in full up front, which enables writing back to the same file in the same pipeline.
Export-Csv, sadly, defaults to ASCII(!) encoding; if your data contains non-ASCII characters, specify an appropriate encoding with -Encoding.
The List Separator is a Windows regional setting.
To change it, please see :
https://support.office.com/en-us/article/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba
Change the separator in all .csv text files In Microsoft Windows,
click the Start button, and then click Control Panel.
Open the dialog box for changing Regional and Language settings.
Type a new separator in the List separator box.
Click OK twice.
Note: After you change the list separator character for your
computer, all programs use the new character as a list separator. You
can change the character back to the default character by following
the same procedure.
You should now be able to change the csv character delimiter.
Please note that you'll need to restart your computer to make the change in effect. You can check your current List Separator value in your Powershell session with (Get-Culture).TextInfo.ListSeparator
You can also check this post, which has a lot of screenshot and different other options on how to do so: https://superuser.com/questions/606272/how-to-get-excel-to-interpret-the-comma-as-a-default-delimiter-in-csv-files
My recommendation is to avoid Excel and use the database objects instead. Example:
[CmdletBinding()]
param(
[Parameter(Position = 0,Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$ExcelFileName,
[Parameter(Position = 1,Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$SheetName
)
$queryString = 'SELECT * FROM [{0}$A1:end]' -f $SheetName
$connectionString = ("Provider=Microsoft.ACE.OLEDB.12.0;" +
"Data Source=$((Get-Item -LiteralPath $ExcelFileName -ErrorAction Stop).FullName);" +
"Extended Properties=Excel 8.0;")
try {
$connection = New-Object Data.OleDb.OleDbConnection($connectionString)
$command = New-Object Data.OleDb.OleDbCommand($queryString)
$command.Connection = $connection
$connection.Open()
$adapter = New-Object Data.OleDb.OleDbDataAdapter($command)
$dataTable = New-Object Data.DataTable
[Void] $adapter.Fill($dataTable)
$dataTable
}
catch [Management.Automation.MethodInvocationException] {
Write-Error $_
}
finally {
$connection.Close()
}
If the above script is Import-ExcelSheet.ps1, you could export to a ;-delimited CSV file by running a command such as:
Import-ExcelSheet "C:\Import Files\ExcelFile.xlsx" "Sheet1" |
Export-Csv C:\Import Files\Test.Csv" --Delimiter ';' -NoTypeInformation
If you have the 32-bit version of Excel installed, you will need to run the above script in the 32-bit version of PowerShell.
If you don't want to license Excel or can't install it on some computer where you want to run the script, you can install the Access database engine instead:
https://www.microsoft.com/en-us/download/details.aspx?id=54920

import xlsx converted to csv to powershell

I need to import csv file to my PS script. In the same script I have a function to convert an xlsx file to csv, which I need to import. But I can't find a way to do that. I tried three approaches:
$CSVLicence = Import-Csv (ConvertXLSX -File $Licence) -Delimiter "," -Encoding UTF8
Where ConvertXLSX is the function to convert xlsx to csv, $Licence is a variable defined at the beginning of the script. The second approach is this:
$CSVfile = ConvertXLSX -File $Licence
$CSVLicence = ImportCsv $CSVfile -Delimiter "," -Encoding UTF8
In both cases I get an error message on the column right after "Import-Csv", that argument Path is null or empty.
The third approach was defining the path literally, which is not really a good solution, but should work, if executor of the file runs it in correct folder and uses correct file name. In this case I get an error "The member "Proemail Exchg Business" is already present" in front of the "Import-Csv" cmdlet
The csv file gets created and it looks precisely as it should, so there's obviously not an error in the converting function.
The ConvertXLSX function is defined like this:
$Licence = "licence"
Function ConvertXLSX ($File)
{
$PWD = "r:\Licence\"
$ExcelFile = $PWD + $File + ".xlsx"
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$wb = $Excel.Workbooks.Open($ExcelFile)
foreach ($ws in $wb.Worksheets | where {$_.name -eq "Pridani"})
{
$ws.SaveAs($PWD + $File + ".csv",6)
}
$Excel.Quit()
}
ConvertXLSX -File $Licence
The function creates the csv file successfully, without any problems
You're currently using the wrong cmdlet. Import-Csv is designed to be used with a file.csv, not a string object or otherwise. There is a cmdlet that exists for that purpose, ConvertFrom-Csv, that takes an -InputObject parameter (indicating it takes pipeline input).
PS C:\> Get-Help -Name 'ConvertFrom-Csv'
SYNTAX
ConvertFrom-Csv [-InputObject] <PSObject[]> [[-Delimiter] <Char>] [-Header <String[]>]
Used in your example:
## Declaration of the delimiter is unnecessary when it's a comma
$CSVLicense = ConvertXLSX -File $Licence | ConvertFrom-Csv
TheIncorrigible1 explained the issue quite well. Now that you've edited the question to add the ConvertXLSX function, we can build on that to get a solution that fits.
$ws.SaveAs($PWD + $File + ".csv",6)
This part is the save to the filename $PWD + $File + ".csv". The number 6 represents the CSV XlFileFormat Enumeration.
The function does not return anything, just performs an action on external file. That's why you are getting the null or empty error. We can get it to return the file path, or to return the data in an object and use ConvertFrom-Csv.
Given your code, the object would require more work as you already have the file path there.
To return file path, change:
}
$Excel.Quit()
}
To:
}
$Excel.Quit()
return $($PWD + $File + ".csv")
}
You code should then work.
You could improve it's readability and conciseness more:
Assign the CSV filename to a variable.
Avoid assigning to $PWD as it's an alias for Get-Location
Get rid of the foreach. You are only getting one sheet and saving that sheet at the moment. If you are doing for multiple sheets, you will have to thing how to pass the multiple paths/object to Import-Csv/ConvertFrom-Csv
Putting it all together:
$Licence = "licence"
Function ConvertXLSX ($File)
{
$rootDir = "r:\Licence\"
$ExcelFile = $rootDir + $File + ".xlsx"
$CSVFile = $ExcelFile.Replace(".xlsx",".csv")
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$wb = $Excel.Workbooks.Open($ExcelFile)
$ws = $wb.Worksheets | where {$_.name -eq "Pridani"})
$ws.SaveAs($CSVFile,6)
$Excel.Quit()
return CSVFile
}
ConvertXLSX -File $Licence
I get an error "The member "Proemail Exchg Business" is already present" in front of the "Import-Csv" cmdlet
This one is handled in this StackOverflow post. Short version is you have two columns with "Proemail Exchg Business" in the first row.
PowerShell creates an object from the CSV: the column headers must be unique. Either fix in the excel sheet, or manually specify unique headers using -Header as in linked post.

Remove known Excel passwords with PowerShell

I have this PowerShell code that loops through Excel files in a specified directory; references a list of known passwords to find the correct one; and then opens, decrypts, and saves that file to a new directory.
But it's not executing as quickly as I'd like (it's part of a larger ETL process and it's a bottleneck). At this point I can remove the passwords faster manually as the script takes ~40 minutes to decrypt 40 workbooks while referencing a list of ~50 passwords.
Is there a cmdlet or function (or something) that's missing which would speed this up, an overlooked flaw in the processing, or is PowerShell, perhaps, just not the right tool for this job?
Original Code (updated code can be found below):
$ErrorActionPreference = "SilentlyContinue"
CLS
# Paths
$encrypted_path = "C:\PoShTest\Encrypted\"
$decrypted_Path = "C:\PoShTest\Decrypted\"
$original_Path = "C:\PoShTest\Originals\"
$password_Path = "C:\PoShTest\Passwords\Passwords.txt"
# Load Password Cache
$arrPasswords = Get-Content -Path $password_Path
# Load File List
$arrFiles = Get-ChildItem $encrypted_path
# Create counter to display progress
[int] $count = ($arrfiles.count -1)
# Loop through each file
$arrFiles| % {
$file = get-item -path $_.fullname
# Display current file
write-host "Processing" $file.name -f "DarkYellow"
write-host "Items remaining: " $count `n
# Excel xlsx
if ($file.Extension -eq ".xlsx") {
# Loop through password cache
$arrPasswords | % {
$passwd = $_
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
# Attempt to open file
$Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd)
$Workbook.Activate()
# if password is correct - Save new file without password to $decrypted_Path
if ($Workbook.Worksheets.count -ne 0) {
$Workbook.Password=$null
$savePath = $decrypted_Path+$file.Name
write-host "Decrypted: " $file.Name -f "DarkGreen"
$Workbook.SaveAs($savePath)
# Close document and Application
$ExcelObj.Workbooks.close()
$ExcelObj.Application.Quit()
# Move original file to $original_Path
move-item $file.fullname -Destination $original_Path -Force
}
else {
# Close document and Application
write-host "PASSWORD NOT FOUND: " $file.name -f "Magenta"
$ExcelObj.Close()
$ExcelObj.Application.Quit()
}
}
}
$count--
# Next File
}
Write-host "`n Processing Complete" -f "Green"
Updated code:
# Get Current EXCEL Process ID's so they are not affected but the scripts cleanup
# SilentlyContinue in case there are no active Excels
$currentExcelProcessIDs = (Get-Process excel -ErrorAction SilentlyContinue).Id
$a = Get-Date
$ErrorActionPreference = "SilentlyContinue"
CLS
# Paths
$encrypted_path = "C:\PoShTest\Encrypted"
$decrypted_Path = "C:\PoShTest\Decrypted\"
$processed_Path = "C:\PoShTest\Processed\"
$password_Path = "C:\PoShTest\Passwords\Passwords.txt"
# Load Password Cache
$arrPasswords = Get-Content -Path $password_Path
# Load File List
$arrFiles = Get-ChildItem $encrypted_path
# Create counter to display progress
[int] $count = ($arrfiles.count -1)
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
# Loop through each file
$arrFiles| % {
$file = get-item -path $_.fullname
# Display current file
write-host "`n Processing" $file.name -f "DarkYellow"
write-host "`n Items remaining: " $count `n
# Excel xlsx
if ($file.Extension -like "*.xls*") {
# Loop through password cache
$arrPasswords | % {
$passwd = $_
# Attempt to open file
$Workbook = $ExcelObj.Workbooks.Open($file.fullname,1,$false,5,$passwd)
$Workbook.Activate()
# if password is correct, remove $passwd from array and save new file without password to $decrypted_Path
if ($Workbook.Worksheets.count -ne 0)
{
$Workbook.Password=$null
$savePath = $decrypted_Path+$file.Name
write-host "Decrypted: " $file.Name -f "DarkGreen"
$Workbook.SaveAs($savePath)
# Added to keep Excel process memory utilization in check
$ExcelObj.Workbooks.close()
# Move original file to $processed_Path
move-item $file.fullname -Destination $processed_Path -Force
}
else {
# Close Document
$ExcelObj.Workbooks.Close()
}
}
}
$count--
# Next File
}
# Close Document and Application
$ExcelObj.Workbooks.close()
$ExcelObj.Application.Quit()
Write-host "`nProcessing Complete!" -f "Green"
Write-host "`nFiles w/o a matching password can be found in the Encrypted folder."
Write-host "`nTime Started : " $a.ToShortTimeString()
Write-host "Time Completed : " $(Get-Date).ToShortTimeString()
Write-host "`nTotal Duration : "
NEW-TIMESPAN –Start $a –End $(Get-Date)
# Remove any stale Excel processes created by this script's execution
Get-Process excel -ErrorAction SilentlyContinue | Where-Object{$currentExcelProcessIDs -notcontains $_.id} | Stop-Process
If nothing else I do see one glaring performance issue that should be easy to address. You are opening a new excel instance for testing each individual password for each document. 40 workbooks with 50 passwords mean you have opened 2000 Excel instances one at a time.
You should be able to keep using the same one without a functionality hit. Get this code out of your inner most loop
# New Excel Object
$ExcelObj = $null
$ExcelObj = New-Object -ComObject Excel.Application
$ExcelObj.Visible = $false
as well as the snippet that would close the process. It would need to be out of the loop as well.
$ExcelObj.Close()
$ExcelObj.Application.Quit()
If that does not help enough you would have to consider doing some sort of parallel processing with jobs etc. I have a basic solution in a CodeReview.SE answer of mine doing something similar.
Basically what it does is run several excels at once where each one works on a chunk of documents which runs faster than one Excel doing them all. Just like I do in the linked answer I caution the automation of Excel COM with PowerShell. COM objects don't always get released properly and locks can be left on files or processes.
You are looping for all 50 passwords regardless of success or not. That means you could find the right password on the first go but you are still going to try the other 49! Set a flag in the loop to break that inner loop when that happens.
As far as the password logic goes you say that
At this point I can remove the passwords faster manually since the script takes ~40 minutes
Why can you do it faster? What do you know that the script does not. I don't see you being able to out perform the script but doing exactly what it does.
With what I see another suggestion would be to keep/track successful passwords and associated file name. So that way when it gets processed again you would know the first password to try.
This solution uses the modules ImportExcel for easier working with Excel files, and PoshRSJob for multithreaded processing.
If you do not have these, install them by running:
Install-Module ImportExcel -scope CurrentUser
Install-Module PoshRSJob -scope CurrentUser
I've raised an issue on the ImportExcel module GitHub page where I've proposed a solution to open encrypted Excel files. The author may propose a better solution (and consider the impact across other functions in the module, but this works for me). For now, you'll need to make a modification to the Import-Excel function yourself:
Open: C:\Username\Documents\WindowsPowerShell\Modules\ImportExcel\2.4.0\ImportExcel.psm1 and scroll to the Import-Excel function. Replace:
[switch]$DataOnly
With
[switch]$DataOnly,
[String]$Password
Then replace the following line:
$xl = New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList $stream
With the code suggested here. This will let you call the Import-Excel function with a -Password parameter.
Next we need our function to repeatedly try and open a singular Excel file using a known set of passwords. Open a PowerShell window and paste in the following function (note: this function has a default output path defined, and also outputs passwords in the verbose stream - make sure no-one is looking over your shoulder or just remove that if you'd prefer):
function Remove-ExcelEncryption
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[String]
$File,
[Parameter(Mandatory=$false)]
[String]
$OutputPath = 'C:\PoShTest\Decrypted',
[Parameter(Mandatory=$true)]
[Array]
$PasswordArray
)
$filename = Split-Path -Path $file -Leaf
foreach($Password in $PasswordArray)
{
Write-Verbose "Attempting to open $file with password: $Password"
try
{
$ExcelData = Import-Excel -path $file -Password $Password -ErrorAction Stop
Write-Verbose "Successfully opened file."
}
catch
{
Write-Verbose "Failed with error $($Error[0].Exception.Message)"
continue
}
try
{
$null = $ExcelData | Export-Excel -Path $OutputPath\$filename
return "Success"
}
catch
{
Write-Warning "Could not save to $OutputPath\$filename"
}
}
}
Finally, we can run code to do the work:
$Start = get-date
$PasswordArray = #('dj7F9vsm','kDZq737b','wrzCgTWk','DqP2KtZ4')
$files = Get-ChildItem -Path 'C:\PoShTest\Encrypted'
$files | Start-RSJob -Name {$_.Name} -ScriptBlock {
Remove-ExcelEncryption -File $_.Fullname -PasswordArray $Using:PasswordArray -Verbose
} -FunctionsToLoad Remove-ExcelEncryption -ModulesToImport Import-Excel | Wait-RSJob | Receive-RSJob
$end = Get-Date
New-TimeSpan -Start $Start -End $end
For me, if the correct password is first in the list it runs in 13 seconds against 128 Excel files. If I call the function in a standard foreach loop, it takes 27 seconds.
To view which files were successfully converted we can inspect the output property on the RSJob objects (this is the output of the Remove-ExcelEncryption function where I've told it to return "Success"):
Get-RSJob | Select-Object -Property Name,Output
Hope that helps.

Creating Powershell script to loop through excel file and create folders

I'm new to Powerscript and looking at writing a solution to loop through an excel file and create a folder for each row in the file.
At the moment I have the following:
$objExcel = new-object -comobject excel.application
$objExcel.Visible = $True
$ExcelFilesLocation = “D:\Users\”
$UserWorkBook = $objExcel.Workbooks.Open($ExcelFilesLocation + “ProjectCodes.xlsx”)
$UserWorksheet = $UserWorkBook.Worksheets.Item(1)
$intRow = 2
Do {
$Projectcode = $UserWorksheet.Cells.Item($intRow, 1).Value()
$pos = $userLogOnEmail.IndexOf(“#”)
$intRow++
} While ($UserWorksheet.Cells.Item($intRow,1).Value() -ne $null)
$objExcel.Quit()
$a = Release-Ref($UserWorksheet)
$a = Release-Ref($UserWorkBook)
$a = Release-Ref($objExcel)
The idea is to loop through the project code column for each row. Then create a folder that is named for the project code.
Having spent painful hours wrangling Excel COM objects with PowerShell, my advice is to give up! Office COM objects are confusing, unwieldy and slow.
Replace the technology you use to access Excel in PowerShell with something better. For example, this module: https://github.com/dfinke/ImportExcel. You can use Install-Module ImportExcel -Scope CurrentUser if you're using PS 5.
Apart from being easier to use and much faster it doesn't require Excel to be installed, making your final script more portable.
Admitedly, you don't get full Excel functionality with this module but since you seem to be doing no more than reading cells, you should be fine.
Alternatively, Save your Excel file as CSV and use Import-Csv instead.
To create a new directory, use New-Item
For example, assuming $Projectcode is a string containing a valid path:
New-Item -Path $Projectcode -Type Directory

Exception calling "Open" with "1" argument(s): ..." in Excel after installing my addin

I have been trying to automate excel automation that installs our company addin in excel and then load the library file (library.xlsm that contains the library macro subroutines and functions). After this for each test, I load the corresponding test.xlsm file and execute the macros. All this, I am doing using powershell (V3). My script gets called on one machine and executes on another remote machine.
This is how I install my addin:
kill -processname excel
$Release1RootDir = $workspace + "\Release1"
$release1Path = Get-ChildItem -Force $release1RootDir
if($release1Path -eq $Null) {
echo "Error: No sub-folder found having MyAddin Installer inside "$release1RootDir
}
else {
$release1 = $release1Path.name.replace('_', '.')
$ExcelAddinInstaller = ($release1Path.FullName + "\MyAddin.msi")
$ExcelAddinTargetDir = ($Release1Path.FullName)
$msiexecPath = "msiexec.exe"
if(Test-Path -Path $ExcelAddinInstaller){
echo "Version for MyAddin inside Release1: "$Release1
$proc = Start-Process $msiexecPath -ArgumentList /x, `"$ExcelAddinInstaller`", TARGETDIR=$ExcelAddinTargetDir, /quiet, /lvx, "D:\Temp\uninstall.log" -Wait
$proc = Start-Process $msiexecPath -ArgumentList /i, `"$ExcelAddinInstaller`", TARGETDIR=$ExcelAddinTargetDir, /quiet, /lvx, "D:\Temp\install.log" -Wait -ErrorAction Stop
echo "Installing addin"
Start-Process "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" -ArgumentList /tlb, "C:\Users\Serviceadmin\Addin\MyAddin\Release\MyAddin.dll"
}
else{
echo $ExcelAddinInstaller
}
}
PS: I am adding the .dll file since, the helper function in my Macros call my C# code.
However, while opening any of these xlsm files, I get the following error:
Exception calling "Open" with "1" argument(s): "Microsoft Excel cannot access the file 'D:\ABC\XYZ\workspace\library.xlsm'. There are
several possible reasons:
The file name or path does not exist.
The file is being used by another program.
The workbook you are trying to save has the same name as a currently open workbook."
At C:\Users\Serviceadmin\AppData\Local\Temp\hudson.ps1:94 char:3
+ $libraryBook = $excel.workbooks.open("$xlLibraryPath\$xlLibraryFileName");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
The above error comes up despite the path of the files being correct. This is how I open my files using powershell:
$excel = new-object -comobject excel.application;
$excel.visible = $true;
$libraryBook = $excel.workbooks.open("$xlLibraryPath\$xlLibraryFileName");
$testWorkbook = $excel.workbooks.open("$testFile")
$excel.Run("$xlLibraryFileName!Initialize", "$testAct")
$loginsuccess = $excel.Run("$xlLibraryFileName!Login", "$xlenvironment", "$xlUserName", "$xlPassword");
if($loginsuccess)
{
$excel.Run("PerformTest");
$excel.Run("$xlLibraryFileName!Logout");
}
$testWorkbook.close($false)
$libraryBook.close($false)
$excel.quit()
I have verified the following:
1. Excel is installed in the required machine- Yes, Excel 2013
2. The path of the xlsm files- All are present
3. Successful installation of the addin
Is there something that I am missing?
Thanks in advance! :)
Well, it turns out that I had to add the folder- "Desktop" in each of the following paths. The solution is quite weird but it is now working for me:
C:\Windows\System32\config\systemprofile\Desktop (should be present for 32 bit machine and even for 64 bit)
C:\Windows\SysWOW64\config\systemprofile\Desktop (only for 64 bit machine)

Resources