user2394966
user2394966

Reputation:

Read Excel data with Powershell and write to a variable

Using PowerShell I would like to capture user input, compare the input to data in an Excel spreadsheet and write the data in corresponding cells to a variable. I am fairly new to PowerShell and can't seem to figure this out. Example would be: A user is prompted for a Store Number, they enter "123". The input is then compared to the data in Column A. The data in the corresponding cells is captured and written to a variable, say $GoLiveDate.

Any help would be greatly appreciated.

Upvotes: 8

Views: 117108

Answers (4)

Mitch B
Mitch B

Reputation: 41

This does not seem to work anymore. I swear it used to, but maybe an update to O365 killed it? or I last used it on Win 7, and have long since moved to Win 10:

$GoLiveDate = $ws.Cells.Item($i, 2).Value

I can still use .Value for writing to a cell, but not for reading it into a variable. instead of the contents of the cell, It returns: "Variant Value (Variant) {get} {set}"

But after some digging, I found this does work to read a cell into a variable:

$GoLiveDate = $ws.Cells.Item($i, 2).Text

In regards to the next question / comment squishy79 asks about slowness, and subsequent OleDB solutions, I can't seem to get those to work in modern OS' either, but my own performance trick is to have all my Excel PowerShell scripts write to a tab delimited .txt file like so:

Add-Content -Path "C:\FileName.txt" -Value $Header1`t$Header2`t$Header3...
Add-Content -Path "C:\FileName.txt" -Value $Data1`t$Data2`t$Data3...
Add-Content -Path "C:\FileName.txt" -Value $Data4`t$Data5`t$Data6...

then when done writing all the data, open the .txt file using the very slow Com "Excel.Application" just to do formatting then SaveAs .xlsx (See comment by SaveAs):

Function OpenInExcelFormatSaveAsXlsx
{
    Param ($FilePath)
    If (Test-Path $FilePath)
    {
        $Excel = New-Object -ComObject Excel.Application
        $Excel.Visible = $true
        $Workbook = $Excel.Workbooks.Open($FilePath)
        $Sheet = $Workbook.ActiveSheet
        $UsedRange = $Sheet.UsedRange
        $RowMax = ($Sheet.UsedRange.Rows).count
        $ColMax = ($Sheet.UsedRange.Columns).count
        # This code gets the Alpha character for Columns, even for AA AB, etc.      
        For ($Col = 1; $Col -le $ColMax; $Col++)
        {
            $Asc = ""
            $Asc1 = ""
            $Asc2 = ""
            If ($Col -lt 27)
            {
                $Asc = ([char]($Col + 64))
                Write-Host "Asc: $Asc"
            }
            Else
            {
                $First = [math]::truncate($Col / 26)
                $Second = $Col - ($First * 26)
                If ($Second -eq 0)
                {
                    $First = ($First - 1)
                    $Second = 26
                }
                $Asc1 = ([char][int]($First + 64))
                $Asc2 = ([char][int]($Second + 64))
                $Asc = "$Asc1$Asc2"
            }
        }
        Write-Host "Col: $Col"
        Write-Host "Asc + 1: $Asc" + "1"
        $Range = $Sheet.Range("a1", "$Asc" + "1")
        $Range.Select() | Out-Null
        $Range.Font.Bold = $true
        $Range.Borders.Item(9).LineStyle = 1
        $Range.Borders.Item(9).Weight = 2
        $UsedRange = $Sheet.UsedRange
        $UsedRange.EntireColumn.AutoFit() | Out-Null
        $SavePath = $FilePath.Replace(".txt", ".xlsx")
        # I found scant documentation, but you need a file format 51 to save a .txt file as .xlsx
        $Workbook.SaveAs($SavePath, 51)
        $Workbook.Close
        $Excel.Quit()
    }
    Else
    {
        Write-Host "File Not Found: $FilePath"
    }
}

$TextFilePath = "C:\ITUtilities\MyTabDelimitedTextFile.txt"
OpenInExcelFormatSaveAsXlsx -FilePath $TextFilePath

If you don't care about formatting, you can just open the tab delimited .txt files as-is in Excel. Of course, this is not very good for inserting data into an existing Excel spreadsheet unless you are OK with having the script rewrite the whole sheet it each time an insert is made. It will still run much faster than using COM in most cases.

Upvotes: 4

Ole Valente
Ole Valente

Reputation: 11

I found this, and Yevgeniy's answer. I had to do a few minor changes to the above function in order for it to work. Most notably the handeling of NULL or empty valued values in the input array. Here is Yevgeniy's code with a few minor changes:

function insert-OLEDBData {
    PARAM (
        [Parameter(Mandatory=$True,Position=1)]
        [string]$file,
        [Parameter(Mandatory=$True,Position=2)]
        [string]$sheet,
        [Parameter(Mandatory=$True,Position=3)]
        [array]$ocol
    )
    $cs = Switch -regex ($file)
    {
        "xlsb$"
            {"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0;HDR=YES`";"}
        "xlsx$"
            {"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"}
    }
    $OLEDBCon = New-Object System.Data.OleDb.OleDbConnection($cs)

    $hdr = $oCol | Get-Member -MemberType NoteProperty,Property | ForEach-Object {$_.name}

    $names = '[' + ($hdr -join "],[") + ']'
    $vals = (@("?")*([array]$hdr).length) -join ','

    $sql = "insert into [$sheet`$] ($names) values ($vals)"

    $sqlCmd = New-Object system.Data.OleDb.OleDbCommand($sql)
    $sqlCmd.connection = $oledbcon

    $cpary = @($null)*([array]$hdr).length

    $i=0
    [array]$hdr|%{([array]$cpary)[$i] = $sqlCmd.parameters.add($_,"VarChar",255);$i++}
    $oledbcon.open()

    for ($i=0;$i -lt ([array]$ocol).length;$i++)
    {
        for ($k=0;$k -lt ([array]$hdr).length;$k++)
        {
            IF (([array]$oCol)[$i].(([array]$hdr)[$k]) -notlike "") {
                ([array]$cpary)[$k].value = ([array]$oCol)[$i].(([array]$hdr)[$k])
            } ELSE {
                ([array]$cpary)[$k].value = ""
            }
        }
        $res = $sqlCmd.ExecuteNonQuery()
    }
    $OLEDBCon.close()
}   

Upvotes: 1

Yevgeniy
Yevgeniy

Reputation: 998

I find it preferable to use an OleDB connection to interact with Excel. It's faster than COM interop and less error prone than import-csv. You can prepare a collection of psobjects (one psobject is one row, each property corresponding to a column) to match your desired target grid and insert it into the Excel file. Similarly, you can insert a DataTable instead of a PSObject collection, but unless you start by retrieving data from some data source, PSObject collection way is usually easier.

Here's a function i use for writing a psobject collection to Excel:

function insert-OLEDBData ($file,$sheet,$ocol) {

    {
        "xlsb$" 
            {"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0;HDR=YES;IMEX=1`";"}
        "xlsx$"
            {"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$File`";Extended Properties=`"Excel 12.0 Xml;HDR=YES;IMEX=1`";"}
    }
    $OLEDBCon = New-Object System.Data.OleDb.OleDbConnection($cs)

    $hdr = $oCol|gm -MemberType NoteProperty|%{$_.name}

    $names = '[' + ($hdr-join"],[") + ']'
    $vals = (@("?")*([array]$hdr).length)-join','

    $sql = "insert into [$sheet`$] ($names) values ($vals)"

    $sqlCmd = New-Object system.Data.OleDb.OleDbCommand($sql)
    $sqlCmd.connection = $oledbcon

    $cpary = @($null)*([array]$hdr).length

    $i=0
    [array]$hdr|%{([array]$cpary)[$i] = $sqlCmd.parameters.add($_,"VarChar",255);$i++}
    $oledbcon.open()

    for ($i=0;$i-lt([array]$ocol).length;$i++)
    {
        for ($k=0;$k-lt([array]$hdr).length;$k++)
        {
            ([array]$cpary)[$k].value = ([array]$oCol)[$i].(([array]$hdr)[$k])
        }
        $res = $sqlCmd.ExecuteNonQuery()
    }
    $OLEDBCon.close()

}

Upvotes: 2

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200203

User input can be read like this:

$num = Read-Host "Store number"

Excel can be handled like this:

$xl = New-Object -COM "Excel.Application"
$xl.Visible = $true
$wb = $xl.Workbooks.Open("C:\path\to\your.xlsx")
$ws = $wb.Sheets.Item(1)

Looking up a value in one column and assigning the corresponding value from another column to a variable could be done like this:

for ($i = 1; $i -le 3; $i++) {
  if ( $ws.Cells.Item($i, 1).Value -eq $num ) {
    $GoLiveDate = $ws.Cells.Item($i, 2).Value
    break
  }
}

Don't forget to clean up after you're done:

$wb.Close()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)

Upvotes: 17

Related Questions