powershell只需将文件粘贴到受监视的文件夹中即可转换csv中的所有excel工作表

时间:2016-01-06 20:30:45

标签: excel csv powershell

这不是一个问题,因为我已经有了答案。我只想对在互联网上发帖和帮助的每个人表示感谢。把我发现的所有东西放在一起真的花了很长时间。

2 个答案:

答案 0 :(得分:0)

此脚本包括: - 通过事件监视文件夹:因此您只需将excel文件粘贴到指定的文件夹(及其子文件夹)中即可自动触发转换。 - Excel(xls / xlsx)到csv。它从该文件夹中的所有excel中获取所有工作表,并将所有内容合并到一个.csv文件中......内容与多个excels /工作表不同,它会引发异常...但它显然可以在更多工作中使用使用单个工作簿的单个excel文件的简单方案。 - 记录捕获的所有不同操作/事件的文件。 - 刷新内存以避免事件侦听问题。

我们走了:

<#-----------------------------------------------------------------------#   
Purpose: To enable users to paste excel/txt files into a single folder and automatically convert them to .csv. 
         The resulting .csv adopts its parent's folder name.
         If more thn 1 excel/txt file is pasted on the same folder, these multiple files are merged into a single .csv file as long as they have the same structure, otherwise
            an exception is raised.
         This script watches constantly these events on a defined folder in this script itself:
                -Creation of files/folders -> Currently enabled.
                -Modification of files/folders -> Currently disabled. (see main region to activate this event)
                -Deletion of files/folders -> Currently disabled. (see main region to activate this event)
                -Renaming of files/folders -> Currently disabled. (see main region to activate this event)
         This script, when run from powershell, will output to console some messages, but it also maintains a physical log file on the server. 
         The location of this log file is defined within the writeToLogFile function.     
#------------------------------------------------------------------------#>




#------------------------------------------------------------------------#
########################## FUNCTIONS REGION ##############################
#------------------------------------------------------------------------#



function writeToLogFile {
    param ([string]$strLogLine = $(throw 'must supply a log line to be inserted!')) #param1: the log line to be inserted/appended to the log

    $logFile = "D:\OneLoader\OneLoaderLog.txt" #Path and name of log file. Modify this if required.

    #checks if log file is greater than 5mb and renames it with system date to allow the creation of a new log file.
    if ((Get-Item $logFile).length -gt 5mb) { #Checks if it's greater than 5 megabytes.
        $renamedLog = $logFile -replace ".txt$", "_$(get-date -Format yyyyMMMdd).txt"  #prepares new log file name, it sufixes sysdate to allow the creation of a new log file.               
        rename-item -path $logFile -newname $renamedLog #renames the current log file.
    } 


    $line = "$(Get-Date): $strLogLine" #Prepares the log line to be inserted: It prefixes the sytem date to whatever you want to insert in the log.

    Write-host $line
    Add-content $logFile -value $line #Appends a log line to the the log file.

}



<#-----------------------------------------------------------------------#
Function getCSVFileName 
Purpose: Given an excel file, it checks for the file's existance and returns a new file name with the name equals to the parent folder and the extension as .csv

Parameters:  
    1) parameter $strFileName: Full path name to an excel file. e.g.: D:\Folder1\ChildFolder\ExcelFile.xlsx      
#------------------------------------------------------------------------#>
function getCSVFileName {

    param ( [string]$strFileName = $(throw 'must supply a file name!'))  #parm1: The excel file name.


    #Test if the path to the excel file is correct. if not, exits the function.
    if (-not (Test-Path $strFileName)) {
        throw "Path $strFileName does not exist."    
    }

    $parentFolder= Split-Path (Split-Path $strFileName -Parent) -Leaf #Obtains the most inner folder name. E.g: C:\Folder1\Folder2\file.txt --> RESULT: Folder2
    #$justFileName = split-path $strFileName -leaf -resolve #Obtains just the file name. E.g: C:\Folder1\Folder2\file.txt --> RESULT: file.txt
    $baseFolder = Split-path $strFileName #Obtainsthe file's base folder name. E.g: C:\Folder1\Folder2\file.txt --> RESULT: C:\Folder1\Folder2

    $fileNameToCSV =  $baseFolder + '\' + $parentFolder + '.csv'  #Build a string for the new .csv file name. The file is renamed to match the parent's folder name.

    return $fileNameToCSV

} #End of function getCSVFileName


<#-----------------------------------------------------------------------#
Function xls-csv 
Purpose: Given an excel file and a sheet name within that file, it converts the contents of that sheet into a .csv file and places it in the same location as the excel file

Parameters:  
    1) parameter $strFileName: Full path name to an excel file. e.g.: D:\Folder1\ChildFolder\ExcelFile.xlsx

#------------------------------------------------------------------------#>
function xls-csv {

    param (
        [string]$strFileName = $(throw 'Must supply a file name!') #parm 1: Excel file. full path.          
    )

    try{
        $newFileNameCSV = getCSVFileName $strFileName #Obtains new .csv file name from function    
    }
    Catch {    
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName

        $logLine="Exception occured while renaming file to csv. FailedItem: $FailedItem. The error message was $ErrorMessage"
        writeToLogFile $logLine #Writes a line to the log

        return
    }

    #Checking if the excel file was not already converted to CSV. If it was, it appends the content of the xls. This is done in case there are more than one excel file in the same directory.


    writeToLogFile "Converting $strFileName" #Writes a line to the log 

    #BEGIN SECTION CONFIG
    #These parameters are required to setup the connection to the OLEDB adapter. Must not change unless necessary.
    $strProvider = "Provider=Microsoft.ACE.OLEDB.12.0"
    $strDataSource = "Data Source = $strFileName"
    $strExtend = "Extended Properties='Excel 12.0;HDR=Yes;IMEX=1';"

    #END SECTION CONFIG

    #BEGIN SECTION CONNECTION
    Try {
        #These steps stablish the connection and the query command that is passed to the OleDB adapter. Must not change unless necessary.
        $objConn = New-Object System.Data.OleDb.OleDbConnection("$strProvider;$strDataSource;$strExtend")
        $sqlCommand = New-Object System.Data.OleDb.OleDbCommand
        $sqlCommand.Connection = $objConn

        $objConn.open()
        #END SECTION CONNECTION


        #BEGIN SECTION SELECT QUERY    

        #Obtains all worksheets within the excel file and converts the content of each one of them into csv            
        $objConn.GetSchema("Tables") | 
        ForEach-Object { 
            if($_.Table_Type -eq "TABLE") 
            {
                $wrksheet= $_.Table_Name 
                $strQuery = "Select * from [$wrksheet]" #Query to read all content from worksheet
                $sqlCommand.CommandText = $strQuery

                $da = New-Object system.Data.OleDb.OleDbDataAdapter($sqlCommand)                
                $dt = New-Object system.Data.datatable

                [void]$da.fill($dt) #fills a datatable with the content of the worksheet

                if (-not (Test-Path $newFileNameCSV )) {    
                    #Pipes the contents of the datatable into a NEW CSV File. Export-Csv function is a native PowerShell function.
                    $dt | Export-Csv $newFileNameCSV -Delimiter ',' -NoTypeInformation    
                } else {
                    #Pipes the contents of the datatable and APPENDS it to the existing CSV File. Export-Csv function is a native PowerShell function.
                    $dt | Export-Csv $newFileNameCSV -Delimiter ',' -NoTypeInformation -Append        
                } #end of if-else


                #END SECTION SELECT QUERY
            } 
     }
      $objConn.close()
    }
    Catch {    
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName

        $logLine = "Exception occured while converting excel file: '$strFileName', worksheet name: $wrksheet --> FailedItem: $FailedItem. The error message was $ErrorMessage"
        writeToLogFile $logLine #Writes a line to the log

        $objConn.close() #close the file in case of errors so it doesn't get locked by a user.

        return
    }

    try {


        #Once and excel file is converted successfully, the extension is changed so it's not picked up again by the script.
        if ($strFileName -like '*.xlsx') { 
            $renamedExcel = $strFileName -replace ".xlsx$", ".old" #Renames xlsx to .old extension
        }else {
            $renamedExcel = $strFileName -replace ".xls$", ".old"  #Renames xls to .old extension
        }

        rename-item -path $strFileName -newname $renamedExcel #changes the extension of the recently converted excel file.
    }    
    Catch {    
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName

        $logLine = "Exception occured while renaming the original file. FailedItem: $FailedItem. The error message was $ErrorMessage"
        writeToLogFile $logLine #Writes a line to the log

        return
    }

    writeToLogFile "Converted $strFileName to $newFileNameCSV" #Writes a line to the log
    return 
} #end of function xls-csv


<#-----------------------------------------------------------------------#
Function txt-csv 
Purpose: Given a txt file, it converts the contents of that txt into a .csv file and places it in the same location as the txt file

Parameters:  
    1) parameter $strFileName: Full path name to a txt file. e.g.: D:\Folder1\ChildFolder\ExcelFile.txt

#------------------------------------------------------------------------#>
function txt-csv {

    param ( [string]$strFileName = $(throw 'Must supply a file name!') ) #parm 1: txt file. full path.             

    try{
        $newFileNameCSV = getCSVFileName $strFileName #Obtains new .csv file name from function    
    }
    Catch {    
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName

        $logLine="Exception occured while renaming file to csv. FailedItem: $FailedItem. The error message was $ErrorMessage"
        writeToLogFile $logLine #Writes a line to the log

        return
    }

    #Checking if the txt file was not already converted to CSV. If it was, it appends the content of the txt. This is done in case there are more than one txt files in the same directory.


    writeToLogFile "Converting $strFileName" #Writes a line to the log 

    try {

        if (-not (Test-Path $newFileNameCSV )) {    
            #Pipes the contents of the txt file into a NEW CSV File. Export-Csv function is a native PowerShell function.
            Import-Csv -Path $strFileName | Export-Csv -Path $newFileNameCSV -Delimiter ',' -NoTypeInformation 
        } else {
            #Pipes the contents of the txt file and APPENDS it to the existing CSV File. Export-Csv function is a native PowerShell function.
            Import-Csv -Path $strFileName | Export-Csv -Path $newFileNameCSV -Delimiter ',' -NoTypeInformation -Append        
        } #end of if-else

        #Once a txt file is converted successfully, the extension is changed so it's not picked up again by the script.
        $renamedFile = $strFileName -replace ".txt$", ".old" #Renames xlsx to .old extension

        rename-item -path $strFileName -newname $renamedFile #changes the extension of the recently converted excel file.
    }    
    Catch {    
        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName

        $logLine = "Exception occured while exporting to csv. FailedItem: $FailedItem. The error message was $ErrorMessage"
        writeToLogFile $logLine #Writes a line to the log

        return
    }

    writeToLogFile "Converted $strFileName to $newFileNameCSV" #Writes a line to the log
    return 
} #end of function txt-csv



<#-----------------------------------------------------------------------#
Function convertDirectory 
Purpose: Given directory, it looks for .xls and .xlsx files recursively and calls the xls-csv function to perform the conversion

Parameters: 
    1) parameter $Directory: Full directory path to scan for excel files      
#------------------------------------------------------------------------#>
function convertDirectory {

    param ( [string]$Directory = $(throw 'Must supply a folder name!'))

    if (-not (Test-Path $Directory)) {
        throw "Path '$Directory' does not exist."
        return
    }    

    #### BEGIN EXCEL CONVERSION SECTION ###
    #Gets list of files within the folder and filters by .xls and .xlsx extensions

    $dir = Get-ChildItem -path $($Directory + "\*") -include *.xls,*.xlsx 

    foreach($file in $dir) #Loops through all excel files
    {       
        writeToLogFile "Found file $file candidate to conversion" #Writes a line to the log
        xls-csv $file.FullName "Sheet1$" #Calls the function to convert the excel file into csv. "Sheet1" is static as of now.
    }
    #### END EXCEL CONVERSION SECTION ###


    #### BEGIN TXT CONVERSION SECTION ###
    #Gets list of files within the folder and filters by .txt extension

    $dir = Get-ChildItem -path $($Directory + "\*") -include *.txt 

    foreach($file in $dir) { #Loops through all excel files        
        writeToLogFile "Found file $file candidate to conversion" #Writes a line to the log
        txt-csv $file.FullName  #Calls the function to convert the txt file into csv. 
    }
    #### END EXCEL CONVERSION SECTION ##

} #end of function convertDirectory



<#-----------------------------------------------------------------------#
Function flushMemory
Purpose: since this is a While{true} script, it may end abruptly. This function is called at beginning of MAIN REGION to clear all possible allocated 
            space of memory and to, more importantly, unregister all posible IO event handlers on the OneLoader directory.

Parameters: None.  
#------------------------------------------------------------------------#>
Function flushMemory {
    # Find out how much memory is being consumed by your Sesssion:
    #[System.gc]::gettotalmemory("forcefullcollection") /1MB   #Uncomment in case of debugging a memory leak

    # Force a collection of memory by the garbage collector:
    [System.gc]::collect()

    # Dump all variables not locked by the system:
    foreach ($i in (ls variable:/*)) {rv -ea 0 $i.Name}  # -verbose $i.Name}  #you can include the verbose argument to get the list of variables out of bound.

    #Check memory usage again and force another collection:
    #[System.gc]::gettotalmemory("forcefullcollection") /1MB #Uncomment in case of debugging a memory leak
    [System.gc]::collect()

    #Check Memory once more:
    #[System.gc]::gettotalmemory("forcefullcollection") /1MB #Uncomment in case of debugging a memory leak

    #Unregister events created by previous instances of this script
    get-eventsubscriber -force | unregister-event -force #THIS LINE IS REALLY IMPORTANT
}


#------------------------------------------------------------------------#
############################ MAIN REGION #################################
#------------------------------------------------------------------------#


writeToLogFile "Script 'OneLoader monitor' initiated at $(Get-Date)" #Writes a line to the log

###IMPORTANT! KEEP CALL TO flushMemory function
flushMemory #!!!!!!IMPORTANT

### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "D:\OneLoader" #IMPORTANT: Defines the OneLoader folder to be monitored. Don't put a slash "\" on the end or a puppy will die.
$watcher.Filter = "*.*" 
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true  


### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = { 
            $eventFullPath = $Event.SourceEventArgs.FullPath #obtains full path of file that got created            
            $changeType = $Event.SourceEventArgs.ChangeType #obtains the type of event captured.            

            $logLine = "$changeType, $eventFullPath" #Prepares the log line that will be inserted in the log file.
            writeToLogFile $logLine #Writes a line to the log

           try{
                $eventBaseDirectory = split-path $eventFullPath  #extracts the base directory from the full path of the file recently created.
                convertDirectory $eventBaseDirectory #calls the function to convert all excel files within the directory caught by the event.
           }
           Catch {    
                $ErrorMessage = $_.Exception.Message
                $FailedItem = $_.Exception.ItemName

                $logLine = "Exception occured while discovering excel files in folder. FailedItem: $FailedItem. The error message was $ErrorMessage"                
                writeToLogFile $logLine #Writes a line to the log

           }
           } #End of $action    

### DECIDE WHICH EVENTS SHOULD BE WATCHED + SET CHECK FREQUENCY.
#Uncomment the events you want this script to monitor over the folder.

$created = Register-ObjectEvent $watcher "Created" -Action $action
#$changed = Register-ObjectEvent $watcher "Changed" -Action $action
#$deleted = Register-ObjectEvent $watcher "Deleted" -Action $action
#$renamed = Register-ObjectEvent $watcher "Renamed" -Action $action

while ($true) {sleep 5}

答案 1 :(得分:0)

function Save-CSVasExcel {
    param (
        [string]$CSVFile = $(Throw 'No file provided.')
    )


    BEGIN {
        function Resolve-FullPath ([string]$Path) {    
            if ( -not ([System.IO.Path]::IsPathRooted($Path)) ) {
                # $Path = Join-Path (Get-Location) $Path
                $Path = "$PWD\$Path"
            }
            [IO.Path]::GetFullPath($Path)
        }

        function Release-Ref ($ref) {
            ([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }

        $CSVFile = Resolve-FullPath $CSVFile
        $xl = New-Object -com 'Excel.Application'
    }

    PROCESS {
        $wb = $xl.workbooks.open($CSVFile)
        $xlOut = $CSVFile -replace '\.csv$', '.xlsx'
        $ws = $wb.Worksheets.Item(1)
        $range = $ws.UsedRange 
        [void]$range.EntireColumn.Autofit()

        $num = 1
        $dir = Split-Path $xlOut
        $base = $(Split-Path $xlOut -Leaf) -replace '\.xlsx$'
        $nextname = $xlOut
        while (Test-Path $nextname) {
            $nextname = Join-Path $dir $($base + "-$num" + '.xlsx')
            $num++
        }

        $wb.SaveAs($nextname, 51)
    }

    END {
        $xl.Quit()

        $null = $ws, $wb, $xl | % {Release-Ref $_}

        # del $CSVFile
    }
}

function Save-ExcelasCSV {
    param (
        [string[]]$files = $(Throw 'No files provided.'),
        [string]$OutFolder,
        [switch]$Overwrite
    )

    BEGIN {
        function Release-Ref ($ref) {
            ([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }

        $xl = New-Object -ComObject 'Excel.Application'
        $xl.DisplayAlerts = $false
        $xl.Visible = $false
    }

    PROCESS {
        foreach ($file in $files) {
            $file = Get-Item $file | ? {$_.Extension -match '^\.xlsx?$'}
            if (!$file) {continue}
            $wb = $xl.Workbooks.Open($file.FullName)

            if ($OutFolder) {
                $CSVfilename = Join-Path $OutFolder ($file.BaseName + '.csv')
            } else {
                $CSVfilename = $file.DirectoryName + '\' + $file.BaseName + '.csv'
            }

            if (!$Overwrite -and (Test-Path $CSVfilename)) {
                $num = 1
                $folder = Split-Path $CSVfilename
                $base = (Split-Path $CSVfilename -Leaf).Substring(0, (Split-Path $CSVfilename -Leaf).LastIndexOf('.'))
                $ext = $CSVfilename.Substring($CSVfilename.LastIndexOf('.'))
                while (Test-Path $CSVfilename) {
                    $CSVfilename = Join-Path $folder $($base + "-$num" + $ext)
                    $num += 1
                }
                $wb.SaveAs($CSVfilename, 6) # 6 -> csv
            } else {
                $wb.SaveAs($CSVfilename, 6) # 6 -> csv
            }

            $wb.Close($True)
            $CSVfilename
        }
    }

    END {
        $xl.Quit()
        $null = $wb, $xl | % {try{ Release-Ref $_ }catch{}}
    }
}

另外,如果Excel文档有多个页面,这可能会有用:

http://www.codeproject.com/Articles/451744/Extract-worksheets-from-Excel-into-separate-files