Powershell excel to csv script works in ISE but not in powershell console - excel

I have a short script that takes certain worksheets out of an excel file and saves them individually as a CSV file.
Function ExcelToCsv ($File) {
$myDir = Split-Path -Parent $($global:MyInvocation.MyCommand.Definition)
$path = "$myDir\csv_files"
If(!(test-path -PathType container $path))
{
Write-Host "Folder csv_files not found, creating..."
New-Item -ItemType Directory -Path $path
}
$excelFile = "$myDir\" + $File + ".xlsx"
$Excel = New-Object -ComObject Excel.Application
$wb = $Excel.Workbooks.Open($excelFile)
$excel.DisplayAlerts = $false;
foreach ($ws in $wb.Worksheets) {
if($ws.name -like '*sheet*') {
$ws.SaveAs("$myDir\csv_files\" + $ws.name + ".csv", 6, 0, 0, 0, 0, 0, 0, 0, $true)
Write-Host "Saved file: $myDir\csv_files\"$ws.name".csv"
} else {
Write-Host "Worksheet "$ws.name" not an correct sheet. Not saved"
}
}
$Excel.Quit()
}
$FileName = "myexcel"
ExcelToCsv -File $FileName
Write-Host "`nCSV files successfully created"
read-host "Press ENTER to exit...."
This is the entire code, not much to it and this is the correct output when run from ISE:
PS U:\excel_to_csv> U:\excel_to_csv\create_csv_files.ps1
Saved file: U:\excel_to_csv\csv_files\ 1sheet .csv
Saved file: U:\excel_to_csv\csv_files\ 2sheet .csv
Saved file: U:\excel_to_csv\csv_files\ 3sheet .csv
CSV files successfully created
Press ENTER to exit....:
But when I use a Powershell console:
Folder csv_files not found, creating...
New-Item : Cannot find drive. A drive with the name 'if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -
Scope Process Bypass }; & 'U' does not exist.
At U:\excel_to_csv\create_csv_files.ps1:7 char:9
+ New-Item -ItemType Directory -Path $path
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (if((Get-Executi... Bypass }; & 'U:String) [New-Item], DriveNotFoundExce
ption
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.NewItemCommand
Ľutujeme, if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'U:\excel_to_csv\
myexcel.xlsx sa nepodarilo nájsť. Je možné, že bol premiestnený, premenovaný alebo odstránený.
At U:\excel_to_csv\create_csv_files.ps1:11 char:5
+ $wb = $Excel.Workbooks.Open($excelFile)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
CSV files successfully created
Press ENTER to exit....:
Some of it is written in Slovakian, it roughly translates to:
Sorry, if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'U:\excel_to_csv\
myexcel.xlsx could not be found. It is possible it was moved, renamed or deleted.
I have tried searching for a solution but nothing worked yet.
I tried making a bash script like this:
powershell.exe -noexit -file U:\create_csv_files.ps1
But that didn't work, not sure if I screwed it up.
As I am on a company computer I am unable to use administrator privileges, but if you believe the lack of them is the problem, I might be able to do something about it.
Thank you for any and all help!

Replace
$myDir = Split-Path -Parent $($global:MyInvocation.MyCommand.Definition)
with
$myDir = $PSScriptRoot
The automatic $PSScriptRoot variable reliably reports the full path of the directory in which the currently executing script is located, irrespective of how the script was invoked. The related $PSCommandPath variable contains the script file's own full path.
Your symptom implies that you invoked your script from outside a PowerShell session, via powershell.exe -Command (with the -Command CLI parameter potentially being positionally implied), in which case $global:MyInvocation.MyCommand.Definition contains the entire command text passed to -Command.

Related

Powershell - Convert .XLS file to .XLSX

I am currently working on a powershell script that converts excel files from .xls to .xlsx
To be precise, I need this to work in some ways:
I need to catch the .xls files FROM a folder and make's a copy to a backup folder
converts them to .xlsx and uploads them to upload folder
Converting them from a folder and uploading them to another folder work's fine, but I tried to add some features and now I'm stuck.
This is the error when I try to run:
At C:\Users\Test\Conv_XLS_V2.ps1:40 char:2
+ }
+ ~ The Try statement is missing its Catch or Finally block. At C:\Users\Test\Conv_XLS_V2.ps1:20 char:16
+ ForEach-Object { ~
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingCatchOrFinally
My code:
# set folders
$downloadfolder = "C:\Users\Test"
#$downloadfolder = "folder that gets the .xls files"
$uploadfolder = "C:\Users\Test\Upload"
#$uploadfolder = "folder that uploads the .xlsx files"
$backupfolder = "C:\Users\Test\Backup"
#$backupfolder = "folder that has .xls files as backup"
#open and convert xls to xlsx
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
write-host $xlFixedFormat
$excel = New-Object -ComObject excel.application
$excel.visible = $true
$filetype ="*xls"
Get-ChildItem -Path $folderpath -Include $filetype -recurse |
ForEach-Object {
try {
$xlsfilename = $_.fullname
#copy file to backup folder
Copy-Item -Path $xlsfilename -Destination $backupfolder
# open the xls
Write-Output "Converting $xlsfilename"
$workbook = $excel.workbooks.open($xlsfilename)
# save converted file (as xlsx)
$xlsxfilename = $xlsfilename + "x"
$workbook.saveas($xlsxfilename, $xlFixedFormat)
$workbook.close()
#remove old file
Write-Output "delete & move file(s)"
Remove-Item -Path $xlsfilename -Force
Move-Item -Path $xlsxfilename -Destination $uploadfolder -Force
# garbage collection
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
# close excel
$excel.Quit()
$excel = $null
Can someone have a look please?
The error message is clear. You forgot to close the try{..} block with an ending bracket } and a try{..} should be followed up by either one or more catch{..} blocks and optionally a finally{..} block.
You can read about that on about Try Catch Finally.
Then, there are some other things wrong and/or can be improved upon in your code as well.
$folderpath is not defined and should be the source folder $downloadfolder
use -Filter instead of -Include as it is much faster. Also you have left out the dot in '*.xls'
append switch -File to the Get-ChildItem cmdlet to make sure you will not receive and try to process directories as well
you can save the converted .xlsx files directly to the uploadfolder, no need to create first and then move
to remove the used COM objects, release them from memory first and then initiate the Garbage Collect.
Do this after you have quit Excel.
# set folders
$downloadfolder = "C:\Users\Test" # folder where the .xls files are
$uploadfolder = "C:\Users\Test\Upload" # folder that uploads the .xlsx files
$backupfolder = "C:\Users\Test\Backup" # folder that has .xls files as backup
# open and convert xls to xlsx
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false # it is much faster if Excel is not visible
# loop through the .xls files and process them
Get-ChildItem -Path $downloadfolder -Filter '*.xls' -Recurse -File |
ForEach-Object {
try {
$xlsfilename = $_.FullName
#copy file to backup folder
Copy-Item -Path $xlsfilename -Destination $backupfolder -Force
# open the xls
Write-Host "Converting $xlsfilename"
$workbook = $excel.Workbooks.Open($xlsfilename)
# save converted file (as xlsx) directly to the upload folder
$newfilename = Join-Path -Path $uploadfolder -ChildPath ('{0}.xlsx' -f $_.BaseName)
$workbook.SaveAs($newfilename, $xlFixedFormat)
$workbook.Close()
#remove old file
Write-Host "Delete old file '$xlsfilename'"
Remove-Item -Path $xlsfilename -Force
}
catch {
# write out a warning as to why something went wrong
Write-Warning "Could not convert '$xlsfilename':`r`n$($_.Exception.Message)"
}
}
# close excel
$excel.Quit()
# garbage collection
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
The error describe a syntax issue. You have included a try { statement without closing it with a } catch {} block. That's all.

Automatically convert xls files to xlsx

I have a software that just allows me to download my data in xls files but I want to use it as an xlsx file.
Currently I have an excel macro when I click on a button it converts all my xls files in xlsx but I want to automate this task so I don't have to open the excel file and click on the button.
I was thinking of a script that start when I log in windows or something like that, and it converts automatically my xls file when I download it. But I'm not very good with scripts so anyone can help me with that ? It's on windows 7 and 10.
Thank you for your help.
Edit:
Here is my Powershell script, now I have to automate it so that it runs automatically when I download a new .xls file, I know I can use the task scheduler but how can I do that automation on en event like adding a new xls file to a folder ? Or maybe we can do it in powershell ?
My script:
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
write-host $xlFixedFormat
$excel = New-Object -ComObject excel.application
$excel.visible = $false
$folderpath = "C:\Users\Mgtspare\Downloads\"
$filetype ="*xls"
Get-ChildItem -Path $folderpath -Include $filetype -recurse |
ForEach-Object `
{
$path = ($_.fullname).substring(0, ($_.FullName).lastindexOf("."))
"Converting $path"
$workbook = $excel.workbooks.open($_.fullname)
$path += ".xlsx"
$workbook.saveas($path, $xlFixedFormat)
$workbook.close()
remove-item $_.fullname
}
$excel.Quit()
$excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
UPDATE:
I changed my script to have a new script faster and I put a watcher so I run the script when a new xls file is downloaded, I will use task manager to run this script when I log in windows so it can watch without doing anything.
Here is my new script:
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Mgtspare\Downloads"
$watcher.Filter = "*.xls"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
$watcher.Path *.xls | rename-item -newname { [io.path]::ChangeExtension($_.name, "xlsx") }
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
Issue:
My script run in the ISE but when i want to run it in cmd or with the right click on my script file and run with with powershell I have this issue
You must provide a value expression on the right-hand side of the '*' operator.
The below script automatically runs each morning with Task Manager
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Mgtspare\Downloads"
#$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
Get-ChildItem -Path C:\Users\Mgtspare\Downloads *.xls | rename-item -newname { [io.path]::ChangeExtension($_.name, "xlsx") }
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher 'Created' -SourceIdentifier 'FileCreated' -Action $action
while ($true) {sleep 1000}

Excel.Application: Microsoft Excel cannot access the file '[<filename>]' There are several possible reasons:

I have a PowerShell script that works, it helps me run multiple queries against multiple servers and save each output in different CSV and then merge them together into an Excel file.
$Servers = get-content -Path "Servers.txt"
$DatabaseName ="master"
#$credential = Get-Credential #Prompt for user credentials
$secpasswd = ConvertTo-SecureString "MyPassword" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ("sa", $secpasswd)
$QueriesFolder = "Queries\"
$ResultFolder = "Results\"
ForEach($Server in $Servers)
{
$DateTime = (Get-Date).tostring("yyyy-MM-dd")
ForEach ($filename in get-childitem -path $QueriesFolder -filter "*.sql" | sort-object {if (($i = $_.BaseName -as [int])) {$i} else {$_}} )
{
$oresults = invoke-sqlcmd -ServerInstance $Server -Database $DatabaseName -Credential $credential -InputFile $filename.fullname
write-host "Executing $filename on $Server"
$BaseNameOnly = Get-Item $filename.fullname | Select-Object -ExpandProperty BaseName
$oresults | export-csv $ResultFolder$BaseNameOnly.csv -NoTypeInformation -Force
}
$All_CSVs = get-childitem -path $ResultFolder -filter "*.csv" | sort-object {if (($i = $_.BaseName -as [int])) {$i} else {$_}}
$Count_CSVs = $All_CSVs.Count
Write-Host "Detected the following CSV files: ($Count_CSVs)"
Write-Host " "$All_CSVs.Name"`n"
$ExcelApp = New-Object -ComObject Excel.Application
$ExcelApp.SheetsInNewWorkbook = $All_CSVs.Count
$output = "C:\Users\FrancescoM\Desktop\CSV\Results\" + $Server + " $DateTime.xlsx"
if (Test-Path $output)
{
Remove-Item $output
Write-Host Removing: $output because it exists already
}
$xlsx = $ExcelApp.Workbooks.Add()
for($i=1;$i -le $Count_CSVs;$i++)
{
$worksheet = $xlsx.Worksheets.Item($i)
$worksheet.Name = $All_CSVs[$i-1].Name
$file = (Import-Csv $All_CSVs[$i-1].FullName)
$file | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | Clip
$worksheet.Cells.Item(1).PasteSpecial()|out-null
}
$xlsx.SaveAs($output)
Write-Host Creating: $output
$ExcelApp.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xlsx) | Out-Null;
Write-Host "Closing all worksheet"
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelApp) | Out-Null;
Write-Host "Closing Excel"
[System.GC]::Collect();
[System.GC]::WaitForPendingFinalizers()
Remove-Item "$ResultFolder\*" -Include *.csv
Write-Host "Cleaning all *.csv"
Start-Sleep -Seconds 3
}
In order to make this script more portable I want all the paths mentioned into it to be stored into a variable and then concatenated.
But as soon as I change:
$output = "C:\Users\FrancescoM\Desktop\CSV\Results\" + $Server + " $DateTime.xlsx"
into:
$output = $ResultFolder + $Server + " $DateTime.xlsx"
things get nasty and I receive the error:
Microsoft Excel cannot access the file 'C:\Users\FrancescoM\Documents\Results\0DC80000'.
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\FrancescoM\Desktop\CSV\QueryLauncher.ps1:50 char:2
+ $xlsx.SaveAs($output)
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
I don't understand, I think I'm concatenating things right.
I also followed this StackOverflow post and restarted my computer after adding "C:\Windows\SysWOW64\config\systemprofile\desktop" but the problem isn't fixed.
How can a variable path mess things up with Excel?
Because you are not defining the full path in the $ResultFolder variable, it will be expanded using the current working directory.
Just look at the path you want it to be:
"C:\Users\FrancescoM\Desktop\CSV\Results\" + $Server + " $DateTime.xlsx"
and the resulting path using the partial $ResultFolder variable:
C:\Users\FrancescoM\Documents\Results\0DC80000
Since you want the output file in a folder on your desktop, set the $output to
$output = Join-Path $([Environment]::GetFolderPath("Desktop")) "CSV\Results\$Server $DateTime.xlsx"
EDIT
From your last comment I understand that you want the output to be in a subfolder called "Results" that resides inside the folder the script itself is in.
In that case do this:
# get the folder this script is running from
$ScriptFolder = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path $MyInvocation.MyCommand.Path }
# the following paths are relative to the path this script is in
$QueriesFolder = Join-Path -Path $ScriptFolder -ChildPath 'Queries'
$ResultFolder = Join-Path -Path $ScriptFolder -ChildPath 'Results'
# make sure the 'Results' folder exists; create if not
if (!(Test-Path -Path $ResultFolder -PathType Container)) {
New-Item -Path $ResultFolder -ItemType Directory | Out-Null
}
Then, when it becomes time to save the xlsx file, create the full path and filename using:
$output = Join-Path -Path $ResultFolder -ChildPath "$Server $DateTime.xlsx"
$xlsx.SaveAs($output)
P.S. I advice to use the Join-Path cmdlet to combine file paths or to make use of [System.IO.Path]::Combine() instead of joining paths together like you do with this line: $oresults | export-csv $ResultFolder$BaseNameOnly.csv. Using the latter can lead to unforeseen pathnames if ever you forget to postfix the first path part with a backslash.
P.S.2 Excel has its own default output path set in Tools->Options->General->Default File Location and has no idea of the relative path for the script. This is why you should save using a Full path and filename.

How to execute powershell script from Excel?

I have fully functional poweshell script, but when I try to run it from MS Excel 2010 I got an error message "You cannot call a method on a null-valued expression" and "cannot index into a null array".
I don't know where is a problem, because as I mentioned the script works without any issues when I don't try to execute it from Excel.
Thank you for any suggestions.
$paths = Get-ChildItem 'E:\TEMP' -Filter '*.txt'
$delete = Get-Content 'E:\TEMP\TEMP1\delete.log'
ForEach ($path in $paths) {
$pathtmp = "$path.tmp"
$sr = New-Object -TypeName System.IO.StreamReader -ArgumentList $path
$sw = New-Object -TypeName System.IO.StreamWriter -ArgumentList $pathtmp
Do {
$line = $sr.ReadLine()
$Column = $line.split(",")
If ($delete -notcontains $Column[1]) {
$sw.WriteLine($line)
}
} Until ( $sr.EndOfStream )
$sr.close()
$sw.close()
Remove-Item $path
Rename-Item $pathtmp $path
}
This script will delete entire row from all text files in a directory if first column of text file matches string from delete.log file.
An issue of the script is that it normally executes only if it being run inside of the directory where the script is placed itself, i think.
In this situation it would be e:\temp\temp1.
This possibly was the reason why excel complains, because working directories are already being set to other places than the script.
If to modify it slightly, it is possible to make it working from anywhere.
One of the possible solutions may be with assigning a full working path to read and write files variables.
Try to use these scripts:
test.ps1 script (inside e:\temp\temp1)
$paths = Get-ChildItem 'e:\TEMP' -Filter '*.txt'
$delete = Get-Content 'e:\TEMP\TEMP1\delete.log'
ForEach ($path in $paths) {
$fpath = $path.fullname
$pathtmp = "$fpath.txt"
$sr = New-Object -TypeName System.IO.StreamReader -ArgumentList "$fpath"
$sw = New-Object -TypeName System.IO.StreamWriter -ArgumentList "$pathtmp"
Do {
$line = $sr.ReadLine()
$Column = $line.split(",")
If ($delete -notcontains $Column[0]) {
$sw.WriteLine($line)
}
} Until ( $sr.EndOfStream )
$sr.close()
$sw.close()
Remove-Item "$fpath"
Rename-Item "$pathtmp" "$fpath"
}
excel macro
Sub test()
Call Shell(Environ$("COMSPEC") & " /c powershell -file E:\temp\temp1\test.ps1", vbNormalFocus)
End Sub

Variable concatenation in wrong order

I have the following:
function createFolder($folderName, $curPath)
{
$dest = $curPath + $folderName
write-host "Path is : " + $dest
# code to mess around with files etc
}
When I run this it gives me the following output:
Path is : + Test_Folder C:\Users\Me
Is there something I'm missing with the + operator, or a join/concat method that is meant for this kind of function? What is the correct way to create/concat/manipulate paths in PowerShell (I've just started using this to automate some cleanup tasks on my desktop).
EDIT: In case it makes a difference, this is what I see when I run the version command:
PS C:\Users\Me> version
BladeLogic Network Shell 8.2.01.273 (Release) [May 12 2012 21:56:02]
Copyright (C) 1996-2012 BladeLogic Inc.
Also, I'm on a work computer with no administrative privileges.
I tried:
$currentPath = "C:\Users\n0223270\Downloads"
$test = "test"
createFolder($test, $currentPath)
function createFolder($folderName, $curPath)
{
$dest = join-path -path $curPath -childpath $folderName
Write-Host $dest
}
This was the following error:
Join-Path : Cannot bind argument to parameter 'Path' because it is null.
At line:4 char:28
+ $dest = join-path -path <<<< $curPath -childpath $folderName
+ CategoryInfo : InvalidData: (:) [Join-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand
createFolder($test, $currentPath)
This isn't how you call a Powershell function. This is passing the first parameter as an array of two strings. The second parameter would be null because it's not specified and there's no default.
Try:
createFolder $test $currentPath

Resources