Search code examples
excelpowershellscriptingwindows-scriptingexcel.application

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
    }

enter image description here

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

enter image description here

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?


Solution

  • 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.