I am trying to figure out how to create a .csv from a PowerShell object that has the appropriate columns. I want to create a .csv file with three columns (User ID, Country, Count). It also needs to sort by User ID and then Country (A to Z). In the example code below, I want the output to be like this:
User ID, Country, Count
Bob, China, 2
Joe, Canada, 1
Joe, United States, 1
$path = "C:\test\test.csv"
Set-Content -Path $path -Value “UserId, Country”
Add-Content -Path $path -Value "Joe, United States", "Bob, China", "Bob, China", "Joe, Canada"
(Import-Csv -Path $path) | Group-Object -Property UserID, Country -NoElement | Select-Object * -ExcludeProperty Values, Group | Select-Object #{Name="User ID, Country";Expression={$_.Name}}, Count | Export-Csv -Path $path -NoTypeInformation -Force
Get-Content -Path $path
Unfortunately, when I run this, User ID and Country are in the same column as below:
"User ID, Country","Count"
"Joe, United States","1"
"Bob, China","2"
"Joe, Canada","1"
I believe the problem stems from Select-Object #{Name="User ID, Country";Expression={$_.Name}}. How can I get this to be in three columns sorted as requested above? As a bit of an aside, I do not fully understand the syntax of the problem code. If you could explain what the #{Name="User ID, Country";Expression={$_.Name}} does, I would appreciate it.
EDIT
Here is my actual code in case this helps with the problem at all.
Set-ExecutionPolicy RemoteSigned
$lastMonth = (get-date).AddMonths(-1)
$startDate = get-date -year $lastMonth.Year -month $lastMonth.Month -day 1
$endDate = ($startDate).AddMonths(1).AddSeconds(-1)
$operation = UserLoginFailed
$path = "C:\Failed Logins $($lastMonth.ToString("yyyy_MM")).csv"
Set-Content -Path $path -Value “UserId, Country”
Function Connect-EXOnline {
$credentials = Get-Credential -Credential $credential
$Session = New-PSSession -ConnectionUri https://outlook.office365.com/powershell-liveid/ `
-ConfigurationName Microsoft.Exchange -Credential $credentials `
-Authentication Basic -AllowRedirection
Import-PSSession $Session -AllowClobber
}
$credential = Get-Credential
Connect-EXOnline
$Logs = #()
do {
$logs += Search-unifiedAuditLog -SessionCommand ReturnLargeSet -SessionId "UALSearch" -ResultSize 5000 -StartDate $startDate -EndDate $endDate -Operations $operation
}while ($Logs.count % 5000 -eq 0 -and $logs.count -ne 0)
$userIds = $logs.userIds | Sort-Object -Unique
foreach ($userId in $userIds) {
$ips = #()
$searchResult = ($logs | Where-Object {$_.userIds -contains $userId}).auditdata | ConvertFrom-Json -ErrorAction SilentlyContinue
$ips = $searchResult.clientip
foreach ($ip in $ips) {
$mergedObject = #{}
$singleResult = $searchResult | Where-Object {$_.clientip -contains $ip} | Select-Object -First 1
Start-sleep -m 400
$ipresult = Invoke-restmethod -method get -uri http://ip-api.com/json/$ip
$UserAgent = $singleResult.extendedproperties.value[0]
$singleResultProperties = $singleResult | Get-Member -MemberType NoteProperty
foreach ($property in $singleResultProperties) {
if ($property.Definition -match "object") {
$string = $singleResult.($property.Name) | ConvertTo-Json -Depth 10
$mergedObject | Add-Member -Name $property.Name -Value $string -MemberType NoteProperty
}
else {$mergedObject | Add-Member -Name $property.Name -Value $singleResult.($property.Name) -MemberType NoteProperty}
}
$property = $null
$ipProperties = $ipresult | get-member -MemberType NoteProperty
foreach ($property in $ipProperties) {
$mergedObject | Add-Member -Name $property.Name -Value $ipresult.($property.Name) -MemberType NoteProperty
}
$mergedObject | Select-Object UserId, Country | Export-Csv $path -Append -NoTypeInformation
}
}
(Import-Csv -Path $path) | Group-Object -Property UserID, Country -NoElement | Select-Object * -ExcludeProperty Values, Group | Select-Object #{Name="User ID, Country";Expression={$_.Name}}, Count | Export-Csv -Path $path -NoTypeInformation
Basically, I will have a .csv file that contains a bunch of email addresses and failed login attempts by country. The last line is used to count how many failed attempts each email address has from each company. This is where the problem is.
I have simplified your problem statement, in order to tackle one issue at a time.
I set out to solve the simple problem of producing the output CSV file you want from
the input CSV file you stated. I created the input file with notepad, instead of with powershell.
Here is what I came up with:
<# Summarizes log by user and country. #>
Import-csv logfile.csv |
Sort-Object UserID, Country |
Group-Object UserID, Country |
foreach { $b = $_.name -split ', '
[PSCustomobject] #{UserID = $b[0]; Country= $b[1]; Count=$_.count}}|
Export-Csv summary.csv
Import-Csv logfile.csv | Format-table
Import-Csv summary.csv | Format-table
The last two steps just display the input and the output CSV files. This is what it looked like when I ran it.
UserID Country
------ -------
Joe United States
Bob China
Bob China
Joe Canada
UserID Country Count
------ ------- -----
Bob China 2
Joe Canada 1
Joe United States 1
As mentioned in my comment, you'll need two calculated properties to split the Name again.
(Import-Csv $path) | Group-Object 'User ID',Country -NoElement |
Select-Object #{n='UserID';e={($_.Name -split ', ')[0]}},
#{n='Country';e={($_.Name -split ', ')[1]}},
Count | Export-Csv $Path -NoTypeInformation
This will yield the same output as Walter Mitty's solution.
You can make your life much easier when you use the availabe built in cmdlets for CSV data like this:
$path = 'C:\test\test.csv'
$Data = #'
UserID,Country,Count
Bob,China,2
Joe,Canada,1
Joe,United States,1
'#
ConvertFrom-Csv -Delimiter ',' -InputObject $Data -OutVariable CSVData
$CSVData | Export-Csv -Path $path -NoTypeInformation -Delimiter ','
You simply build your data as you need it completely and you convert it or export it in once.
Related
I need to list users of a connected drive and it's serial # in an output file. I'll be connecting between 12-24 drives in arrays at a time. I would like to be able to put the assigned drive letters into a variable. And then have the entire script loop for each connected drive. dumping serial + linking it to the users of that drive in a CSV output file
How can I put the assigned drive letters into an array?
$(get-physicaldisk; get-childitem -path (array variable):\Users) | add-content C:\path\to\my\output.csv
almost gets the output I need when I try this on a single drive. But I'd really like to clean it up and only display the important info (PSChildName) excluding all default, public admin accounts to reduce duplicate un-needed info.
I wanted this to work
$(get-physicaldisk | select-object FriendlyName, SerialNumber)-$(get-childitem -path L:\Users| select-object PSChildName)
but it did not
I need it to grab the serial for each drive - and output the users associated with that drive … i'm struggling with making the output look the way I want.
For each - drive in array - output ((serial #) + (users on the drive)) amending my .csv
After much plugging and chugging i'm now here, thanks to everyone's help
function Get-UsersOnDrive([string[]]$DriveLetters){
if (!$DriveLetters){
$DriveLetters = Get-WmiObject Win32_Logicaldisk | %{$_.Name -replace ":", ""}
}
foreach($DriveLetter in $DriveLetters)
{
$SerialNumber = get-partition -DriveLetter $DriveLetter -ErrorAction Ignore | get-disk | select -ExpandProperty SerialNumber
$path = $DriveLetter + ":\Users"
$Users = get-childitem -path $path | select-object PSChildName
$Users | %{
$OutPut = new-object PsCustomObject
$OutPut | Add-Member -MemberType NoteProperty -Name SerialNumber -Value $SerialNumber -PassThru |
Add-Member -MemberType NoteProperty -Name Username -Value $_
return $OutPut
}
}
}
Get-UsersOnDrive -DriveLetters #("C") | Export-Csv -Path C:\sample\Test.csv -NoTypeInformation
Ok so here is what i came up with and its rough
Get-WmiObject Win32_Logicaldisk | %{
$DriveLetter = $_.Name -replace ":", ""
$SerialNumber = get-partition -DriveLetter $DriveLetter | get-disk | select -ExpandProperty SerialNumber
$Users = Get-WmiObject Win32_UserProfile | select -ExpandProperty LocalPath | ?{$_ -like "$DriveLetter*"} | %{
$_ -replace '.*\\'
}
$Users | %{
$OutPut = new-object PsCustomObject
$OutPut | Add-Member -MemberType NoteProperty -Name SerialNumber -Value $SerialNumber -PassThru |
Add-Member -MemberType NoteProperty -Name Username -Value $_
return $OutPut
}
} | Export-Csv -Path C:\sample\Test.csv -NoTypeInformation
A. Get WMI LogicalDisk (gets you the drive letters)
B. Pass the $DriveLetter into a get-partition and get the SerialNumber property value.
C. Get Users Profile path, then find the ones on the current drive and replace everything except for the last slash, which is the username
D. Foreach user on drive we create a Custom Object and add the properties SerialNumber and Username, then return output and export to CSV
Here is a function that you can call to get users on drive as well
function Get-UsersOnDrive([string[]]$DriveLetters){
if (!$DriveLetters){
$DriveLetters = Get-WmiObject Win32_Logicaldisk | %{$_.Name -replace ":", ""}
}
foreach($DriveLetter in $DriveLetters){
$SerialNumber = get-partition -DriveLetter $DriveLetter -ErrorAction Ignore | get-disk | select -ExpandProperty SerialNumber
$Users = Get-WmiObject Win32_UserProfile | select -ExpandProperty LocalPath | ?{$_ -like "$DriveLetter*"} | %{
$_ -replace '.*\\'
}
$Users | %{
$OutPut = new-object PsCustomObject
$OutPut | Add-Member -MemberType NoteProperty -Name SerialNumber -Value $SerialNumber -PassThru |
Add-Member -MemberType NoteProperty -Name Username -Value $_
return $OutPut
}
}
}
Get-UsersOnDrive -DriveLetters #("C","V","F") | Export-Csv -Path C:\sample\Test.csv -NoTypeInformation
If you remove -DriveLetters parameter and the drives then it will check all drives
The following code gets the disk serial number. I am not sure why that is needed. Will this give you a start?
function Get-DiskSerialNumber {
param(
[Parameter(Mandatory = $true,Position=0)]
[string] $DriveLetter
)
Get-CimInstance -ClassName Win32_DiskDrive |
Get-CimAssociatedInstance -Association Win32_DiskDriveToDiskPartition |
Get-CimAssociatedInstance -Association Win32_LogicalDiskToPartition |
Where-Object DeviceId -eq $DriveLetter |
Get-CimAssociatedInstance -Association Win32_LogicalDiskToPartition |
Get-CimAssociatedInstance -Association Win32_DiskDriveToDiskPartition |
Select-Object -Property SerialNumber
}
& openfiles /query /fo csv |
Select-Object -Skip 5 |
ConvertFrom-Csv -Header #('ID','USER','TYPE','PATH') |
Select-Object -Property USER,#{name='DRIVE';expression={$_.PATH.Substring(0,2)}} |
Sort-Object -Property DRIVE,USER -Unique |
Select-Object -Property *,
#{name='SERIALNUMBER';expression={(Get-DiskSerialNumber -Drive $_.DRIVE).SerialNumber}}
I have a script here that will return the information I need from the certificates binded in IIS from different web servers.
$Date = Get-Date
$servers = Get-Content C:\servers.txt
$cert = Foreach ($server in $servers) {
Invoke-Command -ComputerName $server -ScriptBlock{
Import-Module WebAdministration; Get-ChildItem -Path IIS:SslBindings | ForEach-Object -Process{
if ($_.Sites)
{
$certificate = Get-ChildItem -Path CERT:LocalMachine\My |
Where-Object -Property Thumbprint -EQ -Value $_.Thumbprint
[PSCustomObject]#{
Sites = $_.Sites.Value
DnsNameList = $certificate.DnsNameList
NotAfter = $certificate.NotAfter
ExpireInDays = ($certificate.NotAfter - (Get-Date)).Days}
}
}
}
}
$cert | Select PSComputerName, DnsNameList, NotAfter, ExpireInDays | Where-Object {$_.ExpireInDays -lt 30} | Out-File C:\results.txt
This is what the output looks like in notepad:
PSComputerName DnsNameList NotAfter ExpireInDays
-------------- ----------- -------- ------------
ComputerName {URL.com} 1/1/2050 11:59:59 PM 11744
It returns a long lists of certificates with the supporting details. What I need to do is to put the details for the certificate\s which is\are expiring within 30 days into another TXT file in order for me to parse the content or attach the file itself in an email notification.
If you think there are more other ways to work around or simplify this script, I'm very open to recommendations. Thanks in advance.
Try something like this, which is taken from a script I use for a similar task:
# Note: It's usually better to filter with Where-Object and then Select-Object
$ExpiringCerts = $cert | Where-Object { $_.ExpireInDays -lt 30 } | Select-Object -Properties PSComputerName, DnsNameList, NotAfter, ExpireInDays;
if ($ExpiringCerts) {
$MailSettings = #{
SmtpServer = 'smtp.example.com';
Port = 25;
UseSsl = $false;
Subject = 'Subject Line';
To = 'to#example.com','other#example.com'
From = 'from#example.com';
Body = $ExpiringCerts | ConvertTo-Html -As Table -Fragment | Out-String;
BodyAsHtml = $true;
};
Send-MailMessage #MailSettings;
}
If you really need the results as a file attachment, then you can save the output to a file and use the -Attachment parameter on Send-MailMessage. Usually for this sort of notification using the email body makes a lot more sense, however.
I'm working on a basic PowerShell script that inputs a pair of dates then gets all accounts with passwords expiring between those times. I'd like to output the data to the console in a way that is compatible with Export-Csv. That way the person running the script can either just view in the console, or get a file.
Here is my script:
[CmdletBinding()]
param(
[string]$StartDate = $(throw "Enter beginning date as MM/DD/YY"),
[string]$EndDate = $(throw "Enter end date as MM/DD/YY")
)
$start = Get-Date($StartDate)
$end = Get-Date($EndDate)
$low = $start.AddDays(-150)
$high = $end.AddDays(-150)
$passusers = Get-ADUser -Filter { PasswordLastSet -gt $low -and PasswordLastSet -lt $high -and userAccountControl -ne '66048' -and userAccountControl -ne '66080' -and enabled -eq $true} -Properties PasswordLastSet,GivenName,DisplayName,mail,LastLogon | Sort-Object -Property DisplayName
$accts = #()
foreach($user in $passusers) {
$passLastSet = [string]$user.PasswordLastSet
$Expiration = (Get-Date($passLastSet)).addDays(150)
$obj = New-Object System.Object
$obj | Add-Member -MemberType NoteProperty -Name Name -Value $user.DisplayName
$obj | Add-Member -MemberType NoteProperty -Name Email -Value $user.mail
$obj | Add-Member -MemberType NoteProperty -Name Expiration -Value $expiration
$accts += $obj
}
Write-Output ($accts | Format-Table | Out-String)
This prints to the console perfectly:
Name Email Expiration
---- ----- ----------
Victor Demon demonv#nsula.edu 1/3/2016 7:16:18 AM
However when called with | Export-Csv it doesn't:
#TYPE System.String
Length
5388
I've tried multiple variations using objects, and data tables, however it seems like I can only get it to work for console or for CSV, not for both.
Replace
Write-Output ($accts | Format-Table | Out-String)
with
$accts
That way your users can run your script any way they like, e.g.
.\your_script.ps1 | Format-Table
.\your_script.ps1 | Format-List
.\your_script.ps1 | Export-Csv
.\your_script.ps1 | Out-GridView
...
Format-Table | Out-String converts your output to a single string whereas Export-Csv expects a list of objects as input (the object properties then become the columns of the CSV). If Export-Csv is fed a string, the only property is Length, so you get a CSV with one column and one record.
$accts | ConvertTo-Csv | Tee -File output.csv | ConvertFrom-Csv
Hello I am trying to if/else and write two separate files, if PST exists then do the following. Export-Csv -NoTypeInformation C:\$UserName-$ComputerName-OpenPSTs-$Date.csv
Else Export-Csv -NoTypeInformation C:\$UserName-$ComputerName-NOPSTs-$Date.csv
Could anyone please suggest.
$Date = Get-Date -format d-M-yyyy
$UserName = $env:USERNAME
$ComputerName = $env:COMPUTERNAME
$Outlook = New-Object -comObject Outlook.Application
$object = $Outlook.Session.Stores | Where {$_.FilePath -like "*.PST"} | Select `
#{Expression={$_.DisplayName}; Label="PST Name in Outlook"},`
#{Expression={$_.FilePath}; Label="PST Location/FileName"},`
#{Expression={$_.IsOpen}; Label="PST Open in Outlook"},`
#{Expression={(Get-Item $_.FilePath).Length / 1KB}; Label="PST File Size (KB)"}
$object | Add-Member -MemberType NoteProperty -Name 'ComputerName' -Value $ComputerName
$object | Add-Member -MemberType NoteProperty -Name 'UserName' -Value $UserName
$object | Export-Csv -NoTypeInformation C:\$UserName-$ComputerName-OpenPSTs-$Date.csv
Start-Sleep 5
Get-Process | Where {$_.Name -like "Outlook*"} | Stop-Process
You could replace the Where-Object filter with a ForEach-Object loop and a nested conditional:
$Outlook.Session.Stores | % {
if ($_.FilePath -like '*.pst') {
$_ | select ... | Export-Csv 'OpenPST.csv' -NoType -Append
} else {
$_ | select ... | Export-Csv 'NoPST.csv' -NoType -Append
}
}
That might not perform too well, though, because it repeatedly appends to the output files. It might be better to just run 2 pipelines with complementary filters:
$stores = $Outlook.Session.Stores
$stores | ? { $_.FilePath -like '*.pst' } | select ... |
Export-Csv 'OpenPST.csv' -NoType
$stores | ? { $_.FilePath -notlike '*.pst' } | select ... |
Export-Csv 'NoPST.csv' -NoType
I have a small script which retrieves the LastLogonTimestamp and the SAMAccount for all users in a particular OU in AD and converts the timestamp to a date and extracts just the date from the string. That part works fine. I then would like to output that to a CSV so it may be opened in Excel and be perfectly formated into columns and look all pretty.
I have tried ConvertTo-Csv and Export-Csv but have been uncuccessful. The problem is I am new to Powershell. This is my first script and I don't fully understand how this works. My script is probably terribly messy and illogical but it does the job so far.
Please help. Thanks.
$userlist = Get-ADUser -SearchBase "OU=IT,DC=whatever,DC=com,DC=au" -Filter * -Properties * | Select-Object -Property Name,LastLogonTimestamp,SAMAccountName | Sort-Object -Property Name
$userlist | ForEach-Object {
$last = $_.LastLogonTimestamp;
$ADName = $_.SAMAccountName;
$tstamp = w32tm /ntte $last;
if($tstamp.Length -lt "40"){}else
{
$ADDate = [DateTime]::Parse($tstamp.Split('-')[1]).ToString("dd/MM/yyyy")
write-host $ADDate;
write-host $ADName;
}
}
You will have to create objects for each user and pipe those to the Export-CSV cmdlet:
$usersList | %{
# current logic
$user = new-object psobject
$user | add-member -membertype noteproperty -name LastLogon -value $last
$user | add-member -membertype noteproperty -name ADName -value $ADName
$user | add-member -membertype noteproperty -name ADDate -value $ADDate
$user
} | export-csv test.csv -notype
Alternative syntax for populating the object:
$properties = #{"LastLogon" = $last; "ADName" = $ADName; "ADDate" = $ADDate}
$user = new-object psobject -property $properties