How to export file properties in csv using powershell - powershell

I wanted to export a csv-File with File-Properties from some tif-Files.
With this command
Get-ChildItem -Recurse C:\tifs\ |
ForEach-Object {$_ | add-member -name "Owner" -membertype noteproperty `
-value (get-acl $_.fullname).owner -passthru} | Sort-Object fullname |
Select FullName,CreationTime,LastWriteTime,Length,Dimensions |
Export-Csv -Force -NoTypeInformation C:\Test\export.csv
I can export a csv just fine. But as soon as I want to add properties like vertical resolution it fails. I don't quite understand why.

In order to get to the "extended" file properties (like Dimension and Resolution metadata) you have to resort to using the Windows Visual Basic shell options from inside PowerShell as Steven helpfully pointed out. Here is a code sample that should give you the result:
$files = #()
$folder = (New-Object -ComObject Shell.Application).namespace("C:\tifs")
# Loop through each file in folder
foreach ($f in $folder.Items()) {
$a = 0
# Print all the available properties (for debugging purposes)
for ($a ; $a -le 266; $a++) {
if($folder.GetDetailsOf($f, $a)) {
Write-Host "Property: $($folder.GetDetailsOf($folder.items, $a))"
Write-Host "Value: $($folder.GetDetailsOf($f, $a))"
Write-Host "Index: $($a)"
}
}
# Store data in custom PowerShell object
$obj = New-Object -TypeName PSOBJECT
# Fill each property with the file metadata (by index number)
$obj | Add-Member -MemberType NoteProperty -Name FullName -Value $folder.GetDetailsOf($f, 194)
$obj | Add-Member -MemberType NoteProperty -Name CreationTime -Value $folder.GetDetailsOf($f, 4)
$obj | Add-Member -MemberType NoteProperty -Name LastWriteTime -Value $folder.GetDetailsOf($f, 5)
$obj | Add-Member -MemberType NoteProperty -Name Length -Value $folder.GetDetailsOf($f, 1)
$obj | Add-Member -MemberType NoteProperty -Name Dimensions -Value $folder.GetDetailsOf($f, 31)
# Add custom object to a collection
$files += $obj
}
# Export collection to CSV
$files | Export-Csv -Force C:\Test\export.csv -NoTypeInformation -Encoding UTF8

From what I can tell there's no obvious PowerShell/.Net approach to getting additional file meta data. However. there are some COM based approaches.
Check Scripting Guys
And they reference this
You will still have to correlate the data. I usually do that by building hash table keyed of same values, in you can index the metadata using the path property, then use the FullName property of the file info objects to reference it, so you can the properties.

Related

Export-Csv doesn't work as I expected

I have written a little script which should be export the output as CSV.
Here is my script:
$Jobs = Get-VBRJob
foreach ($Job in $Jobs) {
$JobName = $Job.Name
$Objects = $Job.GetObjectsInJob()
$RestorePoints = Get-VBRRestorePoint -Backup $JobName
$Day = $Job.ScheduleOptions.OptionsDaily.DaysSrv
$RP = $RestorePoints.Count
$VM = $Objects.Name
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "JobName" -Value $JobName
$obj | Add-Member -MemberType NoteProperty -Name "Objects" -Value $Objects
$obj | Add-Member -MemberType NoteProperty -Name "RestorePoints" -Value $RestorePoints
$obj | Add-Member -MemberType NoteProperty -Name "Day" -Value $Day
$obj | Add-Member -MemberType NoteProperty -Name "VM" -Value $VM
}
$obj | Export-Csv $path -NoType
I read something about doing it via New-Object PSObject, so I tried this, but the CSV has only one line and returns the types of the attributes not the value. Only "JobName" and "VM" is working fine.
Can anyone help me to get the value of "Objects", "RestorePoints" and "Day" into CSV?
$obj contains only the object you just created, so after the loop completes, the variable holds the last object created in the loop, which is then exported to the CSV. A better approach would be outputting the created objects in the loop and collecting the loop output in a variable. I'd also recommend avoiding Add-Member unless you need to add members to an object that had been created elsewhere.
$obj = foreach ($Job in $Jobs) {
...
New-Object -Type PSObject -Property #{
'JobName' = $JobName
'Objects' = $Objects
'RestorePoints' = $RestorePoints
'Day' = $Day
'VM' = $VM
}
}
Also, if the values you're assigning to the new object's properties are objects themselves, PowerShell will export the string representation of those objects to the CSV, which usually is the full name of the object's class. If you want particular values in the output you probably need to expand those further (e.g. 'VM' = $VM.Name). What exactly you need to do there depends on the actual object, though, so I can't help much there without knowing more about the structure of the objects.

Test a Large Number of Registry Values in Powershell

Testing that individual registry values exist and checking them for the proper data is not overly-complicated in Powershell using Get-Item and Get-ItemProperty. What I would like to be able to do is check a large number of registry values for existence as well as data.
For example, given the following registry entries:
HKLM:\SOFTWARE\Breakfast\1001 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1003 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1004 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1005 = 1 [DWORD]
A big, ugly script that performs the test on each value and data individually isn't complicated, but I'd love to see if it is possible to throw a friendly name, registry path/value, and the desired data into an array so that we could have a function that would perform our tests.
The array could look something like this:
$registry_list = #()
$registry_list.gettype()
$registry_list += ,#('Poptarts','HKLM:\SOFTWARE\Breakfast\','1001','3')
$registry_list += ,#('Toast','HKLM:\SOFTWARE\Breakfast\','1002','3')
$registry_list += ,#('Muffins','HKLM:\SOFTWARE\Breakfast\','1003','3')
$registry_list += ,#('Bagels','HKLM:\SOFTWARE\Breakfast\','1004','3')
$registry_list += ,#('Biscuits','HKLM:\SOFTWARE\Breakfast\','1005','3')
Since I'm new to arrays, I have no idea how to feed these into a function that can output something showing only the errors
Toast
Value Missing (HKLM:\SOFTWARE\Breakfast\1002)
Biscuits
Value Set Incorrectly (HKLM:\SOFTWARE\Breakfast\1005) Desired: 3. Actual: 1
If anyone can weigh in to help figure out how a function or similar can iterate through each of the registry values it would be appreciated. The examples here are short, but I really want to be able to run hundreds of registry values through this test.
I've never been a huge fan of multidimensional arrays in PowerShell. They end up feeling very flaky or unstable. Arrays in PowerShell also suck because when you use the += operator, the system has to build a new array with the new element and then throw the old array away. It's computationally expensive.
For this case, I would create an ArrayList, and add the arrays to that. I would also probably use a HashTable for each item so I could use a name instead of an index number to refer to the items:
$registry_list = New-Object System.Collections.ArrayList;
# Use the Add() function to add records. The [void] type is here because the function
# normally returns the number of records in the ArrayList, and we don't want that to go to output.
[void]$registry_list.Add(#{Value='Poptarts';Path='HKLM:\SOFTWARE\Breakfast';Key='1001';Data='3'});
[void]$registry_list.Add(#{Value='Toast';Path='HKLM:\SOFTWARE\Breakfast';Key='1002';Data='3'});
$registry_list | ForEach-Object {
$RegistryPath = Join-Path -Path $_.Path -ChildPath $_.Key;
if (Test-Path -Path $RegistryPath) {
Write-Host "Path '$RegistryPath' exists."
$RegistryData = (Get-ItemProperty -Path $RegistryPath).($_.Value)
if ($RegistryData -eq $_.Data) {
Write-Host "Check OK. Value $($_.Value) data is set to '$RegistryData'. Desired data is '$($_.Data)'."
}
else {
Write-Host "Check Failed. Value $($_.Value) data is set to '$RegistryData'. Desired data is '$($_.Data)'."
}
}
else {
Write-Host "Path '$RegistryPath' does not exist."
}
}
Note that I have not rigorously tested this code. Notably, I'm a bit skeptical about how correct if ($RegistryData -eq $_.Data) is for all cases.
I would store the data in a CSV file:
friendlyName,key,value,data
Poptarts,HKLM:\SOFTWARE\Breakfast\,1001,3
Toast,HKLM:\SOFTWARE\Breakfast\,1002,3
Muffins,HKLM:\SOFTWARE\Breakfast\,1003,3
Bagels,HKLM:\SOFTWARE\Breakfast\,1004,3
Biscuits,HKLM:\SOFTWARE\Breakfast\,1005,3
Then loop over each row in the file
foreach($row in Import-Csv .\breakfast.csv)
{
# retrieve key
$key = Get-Item $row.key
# retrieve value
$value = $key |Get-ItemProperty -Name $row.value
# compare data
$valid = $value."$($row.value)" -eq $row.data
# output result
$outParams = #{
Object = if($valid){"$($row.friendlyName) is correct"} else {"$($row.friendlyName) is incorrect"}
ForegroundColor = #('Red','Green')[+$valid]
}
Write-Host #outParams
}
I'll leave implementation of error handling and nicer output an excercise for OP :-)
I'd highly suggest when using large arrays to use an array of objects. Creating objects makes referencing the different parts of your array very easy using properties.
You can also then use a template server that has the proper values already in place to then build the object/array of objects to then use to validate other systems.
Here is a basic example of building an object. It is more efficient to create a simple function that builds these objects for you so that you don't have so much code repetition, but this is basic way to go about it. If you want a more advanced method of creating objects, let me know and I'll post an example.
$registrySet = #()
$registryObj = New-Object -TypeName psobject
$registryObj | Add-Member -MemberType NoteProperty -Name Name -Value 'Toast'
$registryObj | Add-Member -MemberType NoteProperty -Name Key -Value 'HKLM:\SOFTWARE\Breakfast\'
$subKeySet = #()
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1001'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '3'
$subKeySet += $subKeyObj
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1002'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '3'
$subKeySet += $subKeyObj
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1003'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '1'
$subKeySet += $subKeyObj
$registryObj | Add-Member -MemberType NoteProperty -Name SubKeySet -Value
$subKeySet
$registrySet += $registryObj
$registrySet | Where {$_.Name -ieq 'toast'} | select SubKeySet

Get-SPContentDatabase export to CSV and include web application url

I'm trying to get a powershell one liner to export the information of all my SharePoint content databases into a CSV file that could then be used as the input for a mounting script.
If i run:
Get-SPContentDatabase | Select Name, DatabaseServer, MaximumSiteCount, WarningSiteCount | Export-CSV DatabaseExport.CSV
I get most of the information I need but I can't get the Web Application url. The WebApplication exports as "SPWebApplication Name=webappname", where I need the url or ID.
I'm thinking I will need to come up with it in a script, that will add loop through and add the url to the exported CSV... but it would be nice to have this in a one liner.
You can use Calculate property (a really cool feature of powershell) ! for you one line script ;)
Get-SPContentDatabase | select Name, Server, MaximumSiteCount, WarningSiteCount , #{Name="URL";Expression={$_.WebApplication.Url}} | Export-Csv TestDB.csv -NoTypeInformation
Well, in case nobody or you come up with a one-line solution, I will share my version of it:
$dbs = Get-SPContentDatabase
$result = #();
foreach($db in $dbs){
$obj = New-Object PSObject
$obj | Add-Member -Type NoteProperty -Name "URL" -Value $db.WebApplication.URL
$obj | Add-Member -Type NoteProperty -Name "Name" -Value $db.Name
$obj | Add-Member -Type NoteProperty -Name "DatabaseServer" -Value $db.Server
$obj | Add-Member -Type NoteProperty -Name "MaximumSiteCount" -Value $db.MaximumSiteCount
$obj | Add-Member -Type NoteProperty -Name "WarningSiteCount" -Value $db.WarningSiteCount
$result += $obj
}
$result | Export-Csv .\DatabaseExport.csv -NoTypeInformation
Just add/remove properties if you need. Check out this list for all the properties that you can use :)

PowerShell to fetch installed programs

I will be hosting a file on a remote server (read-only) and asking people to run the file on their machines to gather installed program information. I want the file to be saved to their Desktop in their user space, so that I can then have them send it to us.
I have the script, but I'm not managing to obtain information from both "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", and "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" in the same output file. I'm obviously missing something inherently obvious, as PowerShell is clearly able to do this, and I'm asking that someone please save me from my PEBKAC issue!
Thank you in advance, appreciated!
Here is my code;
$computers = "$env:computername"
$array = #()
foreach($pc in $computers){
$computername=$pc
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$UninstallKey="Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computername)
$regkey=$reg.OpenSubKey($UninstallKey)
$subkeys=$regkey.GetSubKeyNames()
Write-Host "$computername"
foreach($key in $subkeys){
$thisKey=$UninstallKey+"\\"+$key
$thisSubKey=$reg.OpenSubKey($thisKey)
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername
$obj | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName"))
$obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion"))
$obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
$obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher"))
$array += $obj
}
}
$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | export-csv C:\Users\$env:username\Desktop\Installed_Apps.csv
Right now the following two lines set the same variable:
$UninstallKey="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$UninstallKey="Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
Use this:
$UninstallKey = #(
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
)
Then wrap the real logic in:
$UninstallKey | ForEach-Object {
$regkey=$reg.OpenSubKey($_)
# the rest of your logic here
}

Odd PowerShell behavior and Add-Member

Can someone help me understand why the $_ works differently for the Add-Member function than it seems to for other PowerShell functions? There must be some nuance that I am missing here.
Using the sample CSV file below, the first 3 examples work fine. The fourth, which seems pretty straightforward, does not. The error Powershell returns is:
The variable '$_' cannot be retrieved because it has not been set.
Is this "normal" or have I miscoded something?
Thanks in advance.
Test.csv
Column1,Column2, Column3
Rock,1,abc
Paper,2,efg
Scissors,3,hij
$obj = Import-CSV "C:\test.csv"
(1) $obj | Add-Member -MemberType NoteProperty -Name IdNumber -value "ROCK" -PassThru| Format-Table
(2) $obj | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name IdNumber -value $_.Column1 ; $_} | Format-Table
(3) $obj | ForEach-Object { Add-Member -InputObject $_ -MemberType NoteProperty -Name IdNumber -value $_.Column1 ; $_ } | Format-Table
(4) $obj | Add-Member -MemberType NoteProperty -Name IdNumber -value $_.Column1 -PassThru| Format-Table
When you do this:
$obj | Add-Member -MemberType NoteProperty -Name IdNumber -value $_.Column1 -PassThru
You are accessing $_ outside of a pipeline bound context e.g. inside a foreach-object expression or within a scriptblock value specified for a pipeline bound parameter. The Value parameter is not pipeline bound. If it were you would be able to do this:
$obj | Add-Member -MemberType NoteProperty -Name IdNumber -value {$_.Column1} -PassThru
As it stands, the easiest way to do what you want is:
$obj | ForEach-Object { Add-Member -Inp $_ NoteProperty -Name IdNumber -Value $_.Column1 -PassThru } | Format-Table