satob.hatenablog.com satob.hatenablog.com
So, how should you write records into .xlsx files with OLEDB?
Requirement and Implementation
- You want to record some data into a
.xlsxfile with relational database style. In other words, you want to use the.xlsxfile as a lightweight database system that can use with out-of-the-box Windows feature. - A schema mapped into a
.xlsxfile, and a table mapped into a sheet in the.xlsxfile. - If a
.xlsxfile or a sheet doesn't exist, it should be newly created automatically. If the file and the sheet already exist, data should be added to the sheet.- First, execute the
CREATE TABLEstatement with a suffix$for the sheet name. If the book and the sheet already exist, this statement will be finished without error. If the book or the sheet doesn't exist, This statement throwsOleDbException. Either way, no new sheet will be created with this operation, so you can test the existence of the sheet safely. - Second, execute the
CREATE TABLEstatement without a suffix$for the sheet name if and only if the firstCREATE TABLEstatement throwsOleDbException. It means the.xlsxfile or the sheet doesn't exist, and the sheet(sheetname)1will not be created with this operation.
- First, execute the
- Erroneous data should be invalidated with datatype constraints. If you put erroneous data in the sheet, some exceptions should be thrown.
- You have to execute
CREATE TABLEfirst even if the sheet already exists. If theCREATE TABLEhas not been executed in a connection, the data type constraint in the sheet will not have an effect. Also, you have to suffix the sheet name with$in theINSERTstatement. Otherwise, it fails if the sheet already exists (it means,CREATE TABLE [sheetname$]was executed).
- You have to execute
- Sometimes you will want to show recorded data in some other format (ex. invoice or receipt).
- You cannot specify the cell format with OLEDB. Even if you prepared the sheet with cells that have specified format, OLEDB will ignore those cells and use new rows to record data. So you have to have two sheets in a book: the first one (sheet M) stores recorded data and another one (sheet V) formats data.
- If you don't want to touch raw data through the
.xlsxfile and want to show only the formatted sheet, it is preferable to hide sheet M. This script works regardless of whether the sheet is hidden or not. Also, it is preferable to point the focus to sheet V. The focus position on the sheet will not be changed withINSERT. - Also, you can protect sheet M and sheet V to avoid unintentional changes. OLEDB can insert records even if the sheet or cells are protected with passwords (confirmed with LibreOffice Calc 7.2.3.2).
Sample code
$fileName = "C:\tmp\SchemaName.xlsx" $sheetName = "table" $provider = "Provider=Microsoft.ACE.OLEDB.12.0" $dataSource = "Data Source = $fileName" $extend = "Extended Properties=Excel 12.0" $checkExistenceSQL = "CREATE TABLE [${sheetName}$] (CHARVALUE CHAR(4), INTVALUE INTEGER)" $ddlSQL = "CREATE TABLE [$sheetName] (CHARVALUE CHAR(4), INTVALUE INTEGER)" $conn = New-Object System.Data.OleDb.OleDbConnection("$provider;$dataSource;$extend") $sqlCommand = New-Object System.Data.OleDb.OleDbCommand $sqlCommand.Connection = $conn $conn.open() try { try { # Check whether the file and the sheet already exist. $sqlCommand.CommandText = $checkExistenceSQL $sqlCommand.ExecuteNonQuery() > $null } catch { try { # Create them when they don't exist. $sqlCommand.CommandText = $ddlSQL $sqlCommand.ExecuteNonQuery() > $null } catch { throw $PSItem } } $insertSQL = "INSERT INTO [${sheetName}$] VALUES ('{0}', '{1}')" $sqlCommand.CommandText = ($insertSQL -F 'ABCD', '123') $sqlCommand.ExecuteNonQuery() > $null } finally { # You have to close the book here. # Otherwise, the file will be stay locked with the PowerShell process. $conn.close() }