ConvertTo-DataRange.ps1

<#PSScriptInfo
 
.VERSION 1.1.3
 
.GUID 1806fcb1-4fb4-45d6-ba11-d70231936654
 
.AUTHOR Chris Carter
 
.COMPANYNAME
 
.COPYRIGHT 2017 Chris Carter
 
.TAGS Excel ComObject ExcelTables ShareWorkbook
 
.LICENSEURI http://creativecommons.org/licenses/by-sa/4.0/
 
.PROJECTURI https://gallery.technet.microsoft.com/Convert-Tables-to-Data-0b89924a
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES SaveAs parameter added so that the original file can be kept untouched. Help information updated with minor corrections Verbose statments added.
 
 
#>
 

<#
.SYNOPSIS
Converts tables in an Excel workbook to data ranges
 
.DESCRIPTION
ConvertTo-DataRange converts tables in all sheets of an Excel workbook to data ranges, and then saves the changes. The path to the workbook to convert is supplied to the Path parameter.
 
Workbooks with tables cannot be shared, so this command is useful in preparing a workbook to be shared.
.PARAMETER Path
Specifies the path of the Excel file to be converted.
.PARAMETER SaveAs
Specifies an alternate path to save the Excel file to in case the original should not be changed.
.INPUTS
System.String
     
 
    You can pipe a string that contains a path to ConvertTo-DataRange
.OUTPUTS
None.
     
 
    ConvertTo-DataRange does not generate any output.
.EXAMPLE
PS C:\> ConvertTo-DataRange -Path '<path to file>\Example.xlsx'
 
This command will convert all of the tables in all of the sheets of the workbook Example.xlsx to data ranges, and save the changes to the file.
.EXAMPLE
PS C:\> ConvertTo-DataRange -Path '<path to file>\Example.xlsx' -SaveAs '<new path to file>\NewBook.xlsx'
 
This command will convert all of the tables in all of the sheets of the workbook Example.xlsx to data ranges, and then save the workbook as NewBook.xlsx while Example.xlsx remains unchanged.
.LINK
New-Object -ComObject
#>



[CmdletBinding(SupportsShouldProcess=$true)]

Param(
    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias("FullName")]
        [String[]]$Path,

    [Parameter(Position=1)]
        [Alias("Sa")]
        [String]$SaveAs
)

Begin {
    try {
        #Open Excel
        Write-Verbose "Opening Excel..."
        $Excel = New-Object -ComObject 'Excel.Application'
        Write-Verbose "Excel opened."
    }
    catch {
        Write-Error "An error occurred while trying to load Excel. Check that Excel is installed on the computer before trying again."
        return
    }

    #Helper function that releases the COM object until the reference number reaches 0
    Function Remove-ComObject  {
        Param([Parameter(ValueFromPipeline=$true)][System.Object]$InputObject)

        Process {
            try {
                #Releases the COM object
                $r = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($InputObject)
            }
            catch { Write-Error "An error occurred trying to remove the COM object."; return }

            if ($r -gt 0) {
                #If references are still extant, call recursively.
                Remove-ComObject -InputObject $InputObject
            }
        }
    }
}

Process {
    #handle arrays
    foreach ($p in $Path) {
        try {
            #Open the file
            Write-Verbose "Opening the file at $p..."
            $Workbook = $Excel.Workbooks.Open($p)
            Write-Verbose "$p opened."
        }
        catch {
            Write-Error "There was an error opening the excel file at $p. Make sure the path is valid, or the file is a valid excel file before trying again."
            continue
        }

        #Go through each sheet in the workbook
        foreach ($Sheet in $Workbook.Worksheets) {
            Write-Verbose "Checking $($Sheet.Name) for tables..."
            Write-Verbose "Found $($Sheet.ListObjects.Count) table(s)"
            $i = 0

            #Note: Tables in Excel used to be call 'lists'.
            #Go through each table in the sheet
            foreach ($List in $Sheet.ListObjects) {
                $i++
                
                if ($PSCmdlet.ShouldProcess("Table $i in sheet $($Sheet.Name)", "Convert to data range ")) {
                    Write-Verbose "Converting table $i to data range..."
                    #Convert each table to a data range
                    $List.Unlist()

                    if ($?) { Write-Verbose "Table converted to data range." }
                    else { Write-Error "There was an error converting the table to a data range." }
                }
            }
            Write-Verbose "Done with $($Sheet.Name)."
        }

        #Save under an alternate name if specified...
        if ($SaveAs) {
            
            #Test directory and filename of SaveAs
            if ((Test-Path (Split-Path $SaveAs -Parent)) -and ((Split-Path $SaveAs -Leaf) -match '\.xls$|\.xlsx$')) {
                #If the directory is valid, save under the alternate name and close
                
                if ($PSCmdlet.ShouldProcess($Workbook.Name, "Save As $(Split-Path $SaveAs -Leaf)")) {
                    Write-Verbose "Saving workbook as $SaveAs..."
                    $Workbook.SaveAs($SaveAs)
                    Write-Verbose "Saved"
                }

                Write-Verbose "Closing file..."
                $Workbook.Close($false)
                Write-Verbose "File Closed."
            }
            else {
                Write-Error "The path supplied to the SaveAs parameter is not in a valid directory or is not the correct extension of an Excel file. Please, check the path and try again."
                $Workbook.Close($false)
                continue
            }
        }
        #...or just save and close
        else {
            if ($PSCmdlet.ShouldProcess($Workbook.Name, "Save")) {
                Write-Verbose "Saving and closing $($Workbook.Name)..."
                $Workbook.Close($true)
                Write-Verbose "File saved and closed."
            }
        }
    }
}
    
End {
    #Quit Excel, and then send to function to release COM object
    Write-Verbose "Closing Excel..."
    $Excel.Quit()
    $Excel | Remove-ComObject
    
    #The below two statements are to help remove the excel.exe process which can be stubbornly held onto.
    #These do not always work.
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
    Write-Verbose "Excel Closed."
}