Get DataTable values from anywhere on Excel spreadsheet using OleDB/Powershell - excel

I have a simple business requirement to design a "Roster" spreadsheet that can be read by a powershell script to extract out data for down stream processes. The spreadsheet has some headers such as FirstName, LastName, Company, StartDate. These headers start on A1 and continue to J1. The spreadsheet is 2007 (xlsm) and contains 1 worksheet with a formatted Table (DataTable?). To the right of the DataTable are a few fields such as NumberOfRecords and ContactInfo.
The powershell script so far works fine as long as the DataTable is the first column/row. If I were to switch the NumberOfRecords & ContactInfo field above or to the left, then it throws off the whole order of record reading.
Below is the current script
function getData {
$excelPath = "c:\temp\Roster.xlsm"
$global:roster = #()
$conn = new-object System.Data.OleDb.OleDbConnection
$conn.ConnectionString = 'Provider=Microsoft.ACE.OLEDB.12.0;Data Source='+$excelPath+';Extended Properties="Excel 12.0 Xml;HDR=YES";'
$cmd = new-object System.Data.OleDb.OleDbCommand("SELECT * FROM [Roster$] WHERE [Valid] = true",$conn)
$conn.open()
$reader = $cmd.ExecuteReader()
while ($reader.Read())
{
$user = new-object object
$user | add-member -MemberType NoteProperty -Name "FirstName" -Value $reader.item(1).ToString()
$user | add-member -MemberType NoteProperty -Name "MName" -Value $reader.item(2).ToString()
$user | add-member -MemberType NoteProperty -Name "LastName" -Value $reader.item(3).ToString()
$user | add-member -MemberType NoteProperty -Name "Company" -Value $reader.item(6).ToString()
$user | add-member -MemberType NoteProperty -Name $reader.GetName(8) -Value $reader.item(8).ToString()
$global:roster += $user
}
$conn.close()
$global:roster | out-gridview
#
}
Is there any way I can reference this DataTable directly? I know in Excel 2010 when formatting data as a table you can give it a name (default Table1). Could I do something like "SELECT Table1.* FROM [Roster$] WHERE [VALUE] = true" ??
My goal is to give a nicely formatted spreadsheet, extracting the data from the formatted table via Powershell.

I found a solution but it requires using COM instead of OLEDB or ADO. I was not able to find any solution online with OLE to call named objects. This solution is a bit better since I can arrange my DataTable anywhere in the worksheet, however I'm not too fond of it since it requires Office to be installed, but basically I can call reference to the Named Table by creating an Excel object and then calling it's ListObjects on the ActiveSheet. This returns the DataTable with all of it's Columns/Rows/Ranges. All I need to do then is loop through each row and column to grab the data.
$excel = new-object -ComObject Excel.Application
$tmp = $excel.Workbooks.Open('c:\temp\Roster.xlsm')
$table = $excel.ActiveSheet.ListObjects | where-object { $_.DisplayName -eq "MyNamedRange" }
$rows = $table.ListRows

Related

Separating values of a column in a CSV in powershell

I have multiple files contain a persons Fullname in a single cell. I would like to separate these names into two columns - first name and Surname
the code I have used to separate values worked in a previous iteration of the script I had but now no longer works
I can't pinpoint where the error lies, can anyone advise?
$path = 'C:\MAY2019correct'
#XYZ
$excelOut = Join-Path -Path $path -ChildPath 'XYZ.csv'
$completedFile = Join-Path -Path $path -ChildPath 'Completed-XYZ.csv'
$defaultValue = 'ABC'
$filter = '*XYZ*'
$excelFile = Get-ChildItem -Path $path -Filter $filter -File |
Select-Object -First 1
$allstaff = #()
if ($excelFile) {
$excel = New-Object -ComObject Excel.Application -Property #{Visible =
$false}
# Open the file
$workbook = $excel.Workbooks.Open($excelFile.FullName)
# Activate the first worksheet
$sheet = $workbook.Sheets.Item(1)
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the first row
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the 2 row
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the 3 row
$workbook.SaveAs($excelOut,6)
# Close workbook and save changes
$workbook.Close($true)
# Quit Excel
$excel.Quit()
# clean-up Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
$headers = 'Element Name','Surname','EmployeeNo','Amount','YTD'
# import the csv file and select all the above headers plus one that is created using a calculated property
$csv = Import-Csv -Path $excelOut -Header $headers -UseCulture | Select-Object *, #{Name = 'Paycentre'; Expression = {$defaultValue}}|
Write-Host "Creating completed csv file '$completedFile'"
$csv | Export-Csv -Path $completedFile -UseCulture -Force -
NoTypeInformation
}
else {
Write-Warning "Could not find a file using filter '$filter' in path
'$path'"
}
foreach($staff in $completedFile)
{
#Get the values from the CSV for this row
$Surname = $staff.Surname
$Surname = $Surname.Substring(0, $Surname.lastIndexOf(' '))
$Initial = $staff.Surname
$Initial = $Initial.Substring($Initial.lastIndexOf(' ') + 1 )
$Firstname = $staff.Surname
$Firstname = $Firstname.Substring($Initial.lastIndexOf(' ') + 1 )
$EmployeeNo = $staff.EmployeeNo
$NINumber = $staff.NINumber
$Amount = $staff.Amount
$Paycentre = $staff.Paycentre
$staff2 = New-Object System.Object
$staff2 | Add-Member -MemberType NoteProperty -Name "Surname" -Value $Surname
$staff2 | Add-Member -MemberType NoteProperty -Name "FirstName" -Value $Firstname
$staff2 | Add-Member -MemberType NoteProperty -Name "EmployeeNo" -Value $EmployeeNo
$staff2 | Add-Member -MemberType NoteProperty -Name "NINumber" -Value $NINumber
$staff2 | Add-Member -MemberType NoteProperty -Name "Amount" -Value $Amount
$staff2 | Add-Member -MemberType NoteProperty -Name "FirstName" -Value $Initial
$staff2 | Add-Member -MemberType NoteProperty -Name "Paycentre" -Value $Paycentre
#add to array
$allstaff += $staff2
}
$allstaff | Export-Csv -NoTypeInformation -Path $completedFile
The first thing I see is that your foreach loop is iterating over the $completedFile variable which appears to be a file path, not a collection.
Should it be foreach ($staff in $csv) instead?
Also, be very wary of dealing with names in code. It's super easy to make poor assumptions that break things. See Falsehoods Programmers Believe About Names for more info there.

How do I split/parse a URL string into an object?

I'm trying to split a URL into an object.
$url = "https://api.somedomain.com/v2/direct-access/producing-entities-details?entity_id=104194825&format=json&page=1
The desired result would be
PS C:\WINDOWS\system32> $url.api
https://api.somedomain.com/v2/direct-access
PS C:\WINDOWS\system32> $url.dataset
producing-entities-details
PS C:\WINDOWS\system32> $url.params
entity_id=104194825&format=json&page=1
I'm sure this can be done in combination with regex, but I am also wondering if this can be done just using built in PowerShell functionality.
No need to mess around with regex. The [uri] type accelerator would do some of that work for you. The other parts seem specific to how you choose to interpret the data and not how URL anatomy works.
PS C:\Users\matt> $url = [uri]"https://api.somedomain.com/v2/direct-access/producing-entities-details?entity_id=104194825&format=json&page=1"
PS C:\Users\matt> $url.Query
?entity_id=104194825&format=json&page=1
You can explore the other properties and see how they will help you. For instance, you might need to build the Segments to get the other parts you are looking for.
PS C:\Users\matt> $url.Segments
/
v2/
direct-access/
producing-entities-details
$url = "https://api.somedomain.com/v2/direct-access/producing-entities-details?entity_id=104194825&format=json&page=1"
$uri = [System.Uri]$url
$ParsedQueryString = [System.Web.HttpUtility]::ParseQueryString($uri.Query)
$i = 0
$queryParams = #()
foreach($QueryStringObject in $ParsedQueryString){
$queryObject = New-Object -TypeName psobject
$queryObject | Add-Member -MemberType NoteProperty -Name Query -Value $QueryStringObject
$queryObject | Add-Member -MemberType NoteProperty -Name Value -Value $ParsedQueryString[$i]
$queryParams += $queryObject
$i++
}
$queryParams
Output:
Query Value
----- -----
entity_id 104194825
format json
page 1

Compiling data into columns to be exported to CSV/excel

I am writing a short script to inventory all servers in our infrastructure, this used to be done manually which means some servers may show up as active in our SQL DB but is actually offline, or vice versa.
What i want to do is query our vCenter, SQL and AD and then compiling the result into a csv to be viewed in Excel (see pic ) http://imgur.com/NnwEM4H
Now here is my question: How do i add the columns and format this properly? I want a seperate row for each servername which means sometimes that row will be a blank/empty in one or more of the columns. The Refrence list will always contain a servername though.
I've tried the following:
$ServerList = #()
foreach ($p in $RefList)
{
$Server = New-Object System.Object
$Server | Add-Member -MemberType NoteProperty -Name RefrenceList -Value $p.Name
$ServerList += $Server
}
foreach ($s in $VMList)
{
$Server = New-Object System.Object
$Server | Add-Member -MemberType NoteProperty -Name vCenter -Value $s.name
$ServerList += $Server
}
Which doesnt create the vCenter column or add any data to it.
I also asked over at reddit/r/powershell and tried the following, which unfortently doesn't work either:
$ServerList = #()
foreach ($p in $RefList)
{
$Server = New-Object System.Object
$Server | Add-Member -MemberType NoteProperty -NameRefrenceList -Value $p.Name
$ServerList += $Server
}
Foreach ($server in $serverlist)
{
if ($vmlist -contains $server)
{
$server | add-member -MemberTypeNoteProperty -Name VMList -Value $Server
}
}
I'm at a loss here, i can't even get the first 2 columns to work..
Any advice i greatly appriceiated
First build the List will all Noteproperties :
$ServerList = $reflist | select Name, SQL,SCCM, AD, VCenter #as needed.
Now you can easily update the fields with your foreach Statements.
foreach ($server in $ServerList)
{
if ($vmlist -contains $server)
{
$Server.VCenter = $Server.Name
}
if ($sqllist -contains $server)
{
$Server.SQL = $Server.Name
}
#....
}
and so on with your other lists.

How to add properties to a PowerShell object from an array

I have a two-dimensional array of property names and values, which I need to add to a PowerShell object.
I have NO issues creating and displaying an object like this using New-Object and Add-Member:
$obj = New-Object PSObject
$obj | Add-Member NoteProperty IName($fiel.IName)
$obj | Add-Member NoteProperty SName($fiel.SName)
$obj | Add-Member NoteProperty Taggy($fiel.Taggy)
$obj | Add-Member NoteProperty Title($fiel.Title)
Write-Output $obj
But when I try something like this:
for ($k=0; $k -lt $fieldsArray.Count; $k++)
{
$itemobj | Add-Member –MemberType NoteProperty –Name $fieldsArray[$k].InternalName –Value $itemki[$j][$fieldsArray[$k].InternalName]
#Write-Host $k
#Write-Host $fieldsArray[$k].InternalName.ToString()
#Write-Host $itemki[$j][$fieldsArray[$k].InternalName]
}
Write-Output $itemobj
The Write-Output $itemobj will return only one property member that should be added without any neat column names.
The commented out parts were added for testing purposes and return correct values for all items.
I also tried
$itemobj | Add-Member NoteProperty $fieldsArray[$k].InternalName.ToString()($fieldsArray[$k].InternalName)
without any improvement.
Why are the other property members not added?
I have the data I need. If I write:
for ($k=0; $k -lt $fieldsArray.Count; $k++)
{
Write-Host $k
Write-Host $fieldsArray[$k].InternalName.ToString()
Write-Host $itemki[$j][$fieldsArray[$k].InternalName]
}
I get:
0 ID 1
1 ContentTypeId 0x0108007345CD807822EA4E85691E5C642F3A27
2 ContentType
3 Title Task0
4 Modified 11/24/2014 12:29:30 PM
And these are exactly the values that I expect and want. The problem is adding them as properties to an object. I think I cannot have a variable as a NotePropertyName, but it's a wild guess based on the results I am getting.
Some of the values in $itemki[$j][$fieldsArray[$k].InternalName] are empty - could it be it?
Forget all the arrays. They were just for the context:
Write-Host $fieldsArray[$k].InternalName.ToString() # Writes out the correct value
Write-Host $itemki[$j][$fieldsArray[$k].InternalName] # writes out the correct value
$itemobj | Add-Member NoteProperty $fieldsArray[$k].InternalName.ToString()($fieldsArray[$k].InternalName) # The values/property are not added
The question is: WHY NOT? Are there any restrictions in Add-Member on passing values as variables? Empty values?
I am still not sure completely, but if you are just focusing on the Add-Member then consider the following.
$fieldsarray = "ID", "ContentTypeID", "ContentType", "Title", "Modified"
$itemki = "1", "0x0108007345CD807822EA4E85691E5C642F3A27", "", "Task0", "11/24/2014 12:29:30 PM"
$itemobj = New-Object pscustomobject
for($k=0;$k -lt $fieldsArray.Count ; $k++)
{
$itemobj | Add-Member NoteProperty $fieldsarray[$k] $itemki[$k]
}
$itemobj
Notice the empty string entry in the array $itemki. This would generate the output.
ID : 1
ContentTypeID : 0x0108007345CD807822EA4E85691E5C642F3A27
ContentType :
Title : Task0
Modified : 11/24/2014 12:29:30 PM
Change the "" to an empty element: "1","0x0108007345CD807822EA4E85691E5C642F3A27",,"Task0","11/24/2014 12:29:30 PM", and you get this output:
ID : 1
ContentTypeID : 0x0108007345CD807822EA4E85691E5C642F3A27
ContentType : {Task0}
Title : 11/24/2014 12:29:30 PM
Modified :
Which is wrong. Then if you change the empty element to $null your output looks much like the first:
ID : 1
ContentTypeID : 0x0108007345CD807822EA4E85691E5C642F3A27
ContentType :
Title : Task0
Modified : 11/24/2014 12:29:30 PM
Concerning your output
You say you only get the last element when you do $itemobj outside the loop. What does $itemobj look like after each pass? for(;;){} loops don't send data to the output stream in the same way that a ForEach-Object{} would which is worth mentioning.
You could use a hash table instead of two arrays. It's very easy to create an object from a hash table. Here's an example:
$hash = #{
ID = '1'
ContentTypeID = '0x0108007345CD807822EA4E85691E5C642F3A27'
ContentType = ''
Title = 'Task0'
Modified = '11/24/2014 12:29:30 PM'
}
$Object = New-Object PSObject -Property $hash
Don't do a For loop, do a ForEach-Object loop. Something like:
$Object = New-Object PSObject
$Array | ForEach{
Add-Member -InputObject $Object -NotePropertyName $_[0] -NotePropertyValue $_[1]
}
That should do what you're looking for I think.

powershellv2 - add new parameter to custom object

I have created a custom Object using the code below, but I now need to add an additional property.
Reading the online documentation and help files suggests that I need to use add-member (as per my example below).
But When I run this I get:
Add-Member : A positional parameter cannot be found that accepts argument 'newvar'.
What is the correct syntax to add a new parameter to an Object.
Also, if I wanted to update a parameter with a different value, can I use the the same add-member? (there doesn't appear to be an 'update-member' cmdlet)
$TestList = #(
"item1"
"item2"
)
$TESTObject = #()
foreach($a in $TestList)
{
$dItem = $a
$TESTObject += New-Object PSObject -property #{
item = "$dItem";
}
}
FOREACH($a in $TESTObject)
{
#DO STUFF HERE
$newVar = 1234
$a | Add-Member newvar $newVar
}
The way I have found to do this is:
Add a member to a custom (PSObject) Object
Add-Member -InputObject $TESTObject -MemberType NoteProperty -Name newVar -Value $newVar
And to update an existing member I use
Add-Member -InputObject $TESTObject -MemberType NoteProperty -Name newVar -Value $newVar -force
whether this is the best way to do this (or even the correct way) I'm not sure. But it seems to work.

Resources