Logon time script | how to overwrite last logon Powershell - powershell

I have a script configured on my GPO that tracks a certain user group their logon times exported to a csv.
Now i've gotten the question, to make it show only the LAST logon time.
At the moment it just writes down every single logon time, but they would like only the last one.
Is there a way to make it overwrite it instead?
Let's say user1 logs in 3 times, i would like to only show it the last one, and that for every user in this group.
The script i have at the moment is a very simple one:
"logon {0} {1} {2:DD-MM-YYYY HH:mm:ss}" -f (Get-Date),$env:username, $env:computername >> \\server\folder\file.csv
Much appreciated if somebody could tell me if it is possible!

First of all, you are appending a line to a text file which is not a CSV file because the values aren't separated by a delimiter character.
Then also, you use the wrong order of values for the -f Format operator: While the template string clearly has the date as the last placeholder in {2:DD-MM-YYYY HH:mm:ss}, you feed that as the first value..
Please also notice that a date format is Case-Sensitive, so you need to change DD-MM-YYYY HH:mm:ss into dd-MM-yyyy HH:mm:ss
I'd advise to change the script you now have to something like:
[PsCustomObject]#{
User = $env:USERNAME
Computer = $env:COMPUTERNAME
LogonDate = '{0:dd-MM-yyyy HH:mm:ss}' -f (Get-Date)
} | Export-Csv -Path '\\server\folder\UserLogon.csv' -NoTypeInformation -Append
Then, when it comes down to reading that file back and preserving only the latest logons, you can do
$data = Import-Csv -Path '\\server\folder\UserLogon.csv' | Group-Object User | ForEach-Object {
$_.Group | Sort-Object {[datetime]::ParseExact($_.LogonDate, 'dd-MM-yyyy HH:mm:ss', $null)} |
Select-Object -Last 1
}
# output on screen
$data
# overwrite the CSV file to keep only the last logons
$data | Export-Csv -Path '\\server\folder\UserLogon.csv' -NoTypeInformation

I think one arrow > should be to overwrite a file.
There is also a command called Out-File with parameters to overwrite a file.
More information on the PowerShell documentation,
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-7.1

I must reiterate what others have commented about this not being the best approach to this problem however, given the output file that you have, you could process it like this to get the last record for a given user:
$last = #{} ; Get-Content \\server\folder\file.csv |% { $token = $_ -split ' ' ; $last.$($token[3]) = $_ }
$last
It creates a hash keyed with the username and updates it with the last line from the file. You can also access $last.foo to get the last entry for user foo.
I'd also note that your CSV isn't a CSV which makes it more difficult to process. You'd be better to use Export-CSV or at least putting the commas in. Also, while still not the best approach, you could create a file per user which you could just overwrite each time they login, thus:
new-object PSObject -Property #{ 'date'= (Get-Date); 'username'= $env:username; 'Computer' = $env:computername } | Export-CSV -Path "\\server\folder\$($env:username).csv" -NoTypeInformation
You could import everything for processing by doing:
gci \\server\folder\*.csv |% { import-csv $_ }

Related

Copy from Specific Folder with Multiple Folders

I am creating a backup and restore tool with powershell script and I am trying to make it so that when restoring, the script picks the last folder created and restores from that directory structure. Basically I am having the script start with making a backup directory with a date/time stamp like so:
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$CurrentDomainName = $CurrentUser.split("\")[0]
$CurrentUserName = $CurrentUser.split("\")[1]
$folderdate = Get-Date -f MMddyyyy_Hm
$homedir = Get-Aduser $CurrentUserName -prop HomeDirectory | select -ExpandProperty
HomeDirectory
New-Item $homedir -Name "TXBackup\$folderdate" -ItemType Directory
$cbookmarks = "$env:userprofile\Appdata\Local\Google\Chrome\User Data\Default\Bookmarks"
md $homedir\TXBackup\$folderdate\Chrome
Copy-Item $cbookmarks "$homedir\TXBackup\$folderdate\Chrome" -Recurse
Backup Folder Structure
Basically everytime someone runs the backup tool it will create a subfolder under the Backup directory with the date/time name to track the latest one. The problem comes when I want to restore from the last one create I can no longer use a $folderdate variable since it will pull the whatever the time is while the tool is being run. Here is the code without taking into account what the last folder is. I tried using sort but that doesn't appear to give me a clear path to select the last one created or I just am such a noob I didn't use it right :(
##Restoring Files from Backup
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$CurrentDomainName = $CurrentUser.split("\")[0]
$CurrentUserName = $CurrentUser.split("\")[1]
$homedir = get-aduser $CurrentUserName -prop HomeDirectory | select -ExpandProperty HomeDirectory
##Restoring Chrome Bookmarks
Sort-Object -Property LastWriteTime |
Select-Object -Last 1
$rbookmarks = "$homedir\TXBackup\$folderdate\Chrome\Bookmarks"
Copy-Item $rbookmarks "C:\Test\"
I know I didn't use that correctly but any direction would be awesome for this newbie :)
You can use Sort-Object with a script block and use [DateTime] methods to parse the date from the folder name, using the same format string you used to create them.
# Sort directory names descending
Get-ChildItem -Directory | Sort-Object -Desc {
# Try to parse the long format first
$dt = [DateTime]::new( 0 )
if( [DateTime]::TryParseExact( $_.Name, 'MMddyyyy_HHmm', [CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::none, [ref] $dt ) ) {
return $dt
}
# Fallback to short format
[DateTime]::ParseExact( $_.Name, 'MMddyyyy', [CultureInfo]::InvariantCulture )
} | Select-Object -First 1 | ForEach-Object Name
Note: I've changed the time format from Hm to HHmm, because Hm would cause a parsing ambiguity, e. g. 01:46 would be formatted as 146, but parsed as 14:06.
Also I would move the year to the beginning, e. g. 20220821_1406, so you could simply sort by name, without having to use a script block. But that is not a problem, just an (in)convenience and you might have a reason to put the year after the day.
Given these folders:
08212022
08222022
08222022_1406
08222022_1322
08222022_1324
08222022_1325
08222022_1343
The code above produces the following output:
08222022_1406
To confirm the ordering is correct, I've removed the Select-Object call:
08222022_1406
08222022_1343
08222022_1325
08222022_1324
08222022_1322
08222022
08212022
Note that the ordering is descending (-Desc), so Select-Object -First 1 can be used to more effectively select the latest folder.

[PowerShell]Get-Content and Add Column Entry?

I am trying to input a list of users into PowerShell and get a specific security group attached to the user's account. At this current time, I have two pieces - an Excel sheet with multiple pieces of data, and a .txt with just the user's usernames. The script I have currently just inputs the user's usernames from the .txt and gets the security group from their account that matches a specific prefix, however I noticed doing it this way doesn't give any specific order. Even though the users are in a specific order (copied and pasted exactly from the excel document), the actual output doesn't come back well.
So, here's what I'd Like to do now, I just don't know how. I would like to get the content from the Excel document, take all of the usernames and do Get-ADPrincipalGroupMembership like I am now, and then write the security group Back to the line that matches the username. For example, if I looked up the SG for msnow, it would get the SG for msnow and then write the SG back to the row that has msnow, and continues through the list. Instead of just doing an Out-GridView, it would actually write this to the Excel document.
Any help on making this work?
Here is the code I have right now.
Import-Module ActiveDirectory
$Names = Get-Content C:\Temp\Users.txt
$Records = #()
Foreach ($ADUsers in $Names) {
Try {
$SG = Get-ADPrincipalGroupMembership -Identity $ADUsers | Select Name | Where {$_.Name -Like "SG - *"}
$SGName = $SG.Name
}
Catch [ADIdentityNotFoundException] {
$SGName = "User not found"
}
$Records += New-Object PSObject -Property #{"UserName" = $ADUsers;"Security Group" = $SGName}
}
Write-Host "Generating CSV File..."
$Records | Out-GridView
Thank you!
If you save the Excel as CSV, so it will look something like
"UserName","Security Group","InsideInfo"
"bloggsj","","tall guy"
"ftastic","","nothing worth mentioning"
things shouldn't be that hard to do.
$out = 'D:\Test\Updated_usersandgroups.csv'
$csv = Import-Csv -Path 'D:\Test\usersandgroups.csv'
Write-Host "Updating CSV File..."
foreach ($user in $csv) {
try {
$SG = Get-ADPrincipalGroupMembership -Identity $user.UserName -ErrorAction Stop
# if more groups are returned, combine them into a delimited string
# I'm using ', ' here, but you can change that to something else of course
$SGName = ($SG | Where-Object {$_.Name -Like "SG - *"}).Name -join ', '
}
catch [ADIdentityNotFoundException] {
$SGName = "User $($user.UserName) not found"
}
catch {
# something else went wrong?
$SGName = $_.Exception.Message
}
# update the 'Security Group' value
$user.'Security Group' = $SGName
}
Write-Host "Generating updated CSV File..."
$csv | Export-Csv -Path $out -UseCulture -NoTypeInformation
# show output on screen
$csv | Format-Table -AutoSize # or -Wrap if there is a lot of data
# show as GridView (sorts by column)
$csv | Out-GridView
Output in console would then look like
UserName Security Group InsideInfo
-------- -------------- ----------
bloggsj SG - Group1, SG - Group1 tall guy
ftastic SG - Group1 nothing worth mentioning
Note: I don't know what delimiter your Excel uses when saving to CSV file. On my Dutch machine, it uses the semi-colon ;, so if in your case this is not a comma, add the delimiter character as parameter to the Import-Csv cmdlet: -Delimiter ';'
Excel uses whatever is set in your locale as ListSeparator for the delimiter character. In PowerShell you can see what that is by doing (Get-Culture).TextInfo.ListSeparator. On output, the -UseCulture switch will make sure it uses that delimiter so Excel will understand

Need to add AD location Info in the script

I have a script for retrieving AD site and subnet info from the forest. Need to add the location details also to the script
The script is tested and working fine where it gives site and subnet details.
$configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
$siteContainerDN = ("CN=Sites," + $configNCDN)
$siteObjs = Get-ADObject -SearchBase $siteContainerDN -filter { objectClass -eq "site" } -properties "siteObjectBL", name
foreach ($siteObj in $siteObjs) {
$subnetArray = New-Object -Type string[] -ArgumentList $siteObj.siteObjectBL.Count
$i = 0
foreach ($subnetDN in $siteObj.siteObjectBL) {
$subnetName = $subnetDN.SubString(3, $subnetDN.IndexOf(",CN=Subnets,CN=Sites,") - 3)
$subnetArray[$i] = $subnetName
$i++
}
$siteSubnetObj = New-Object PSCustomObject | Select SiteName, Subnets
$siteSubnetObj.SiteName = $siteObj.Name
$siteSubnetObj.Subnets = $subnetArray
$file = "C:\temp\1.csv"
Out-File $file -encoding ASCII -input $siteSubnetObj -append
}
I expect to pull out AD location details also using the script.
You can shorten this script by using the Get-ADReplicationSite command. I would also consider using Export-Csv since you are outputting objects to file.
Get-ADReplicationSite -Filter * -Properties Subnets,Location |
Select #{n='SiteName';e={$_.Name}},
#{n='Subnets';e={$_.Subnets -replace "^CN=(.*?),CN=Subnets,.*$",'$1'}},Location |
Export-Csv -Path 'C:\temp\1.csv' -encoding ASCII -NoTypeInformation
Export-Csv will create a comma delimited file by default (the delimiter is changeable) with the first line (headers) being the property names of your objects. Each other line will contain comma separated values for each of those properties. The columns for properties and values will line up perfectly.
If you have more than 4 subnets per site, the Out-File method alone without changing anything else will cut off the subnet values. You would need to set $formatenumerationlimit to something higher than 4 or -1 for unlimited or make sure the output is not in table format. It will be much harder to work with this file if you don't use Export-Csv because there will not be a consistent delimiter between item properties and their values.
I can add location details to this if you explain exactly what that is.
I do not know what are you referring to with "Location" so can't help there.
Also, I understand the file output is easier to read that way and is probably consumed by a human as a report but you have to consider Out-file will default to your screen width when redirecting to file so the number of subnets which will be saved will depend on that width (and not on a fixed value such as 4). To enlarge the output width you can use the -width parameter
$Something | out-file $file -width 600
or set the default width:
$PSDefaultParameterValues=#{"Out-File:Width"="600"}
Note that large numbers may have undesired side effects.

Powershell, Loop through CSV files and search for a string in a row, then Export

I have a directory on a server called 'servername'. In that directory, I have subdirectories whose name is a date. In those date directories, I have about 150 .csv file audit logs.
I have a partially working script that starts from inside the date directory, enumerates and loops through the .csv's and searches for a string in a column. Im trying to get it to export the row for each match then go on to the next file.
$files = Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525'
ForEach ($file in $files) {
$Result = If (import-csv $file.FullName | Where {$_.'path/from' -like "*01May18.xlsx*"})
{
$result | Export-CSV -Path c:\temp\output.csv -Append}
}
What I am doing is searching the 'path\from' column for a string - like a file name. The column contains data that is always some form of \folder\folder\folder\filename.xls. I am searching for a specific filename and for all instances of that file name in that column in that file.
My issue is getting that row exported - export.csv is always empty. Id also like to start a directory 'up' and go through each date directory, parse, export, then go on to the next directory and files.
If I break it down to just one file and get it out of the IF it seems to give me a result so I think im getting something wrong in the IF or For-each but apparently thats above my paygrade - cant figure it out....
Thanks in advance for any assistance,
RichardX
The issue is your If block, when you say $Result = If () {$Result | ...} you are saying that the new $Result is equal what's returned from the if statement. Since $Result hasn't been defined yet, this is $Result = If () {$null | ...} which is why you are getting a blank line.
The If block isn't even needed. you filter your csv with Where-Object already, just keep passing those objects down the pipeline to the export.
Since it sounds like you are just running this against all the child folders of the parent, sounds like you could just use the -Recurse parameter of Get-ChildItem
Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\' -Recurse |
ForEach-Object {
Import-csv $_.FullName |
Where-Object {$_.'path/from' -like "*01May18.xlsx*"}
} | Export-CSV -Path c:\temp\output.csv
(I used a ForEach-Object loop rather than foreach just demonstrate objects being passed down the pipeline in another way)
Edit: Removed append per Bill_Stewart's suggestion. Will write out all entries for the the recursed folders in the run. Will overwrite on next run.
I don't see a need for appending the CSV file? How about:
Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525' | ForEach-Object {
Import-Csv $_.FullName | Where-Object { $_.'path/from' -like '*01May18.xlsx*' }
} | Export-Csv 'C:\Temp\Output.csv' -NoTypeInformation
Assuming your CSVs are in the same format and that your search text is not likely to be present in any other columns you could use a Select-String instead of Import-Csv. So instead of converting string to object and back to string again, you can just process as strings. You would need to add an additional line to fake the header row, something like this:
$files = Get-ChildItem '\\servername\volume\dir1\audit\serverbeingaudited\20180525'
$result = #()
$result += Get-Content $files[0] -TotalCount 1
$result += ($files | Select-String -Pattern '01May18\.xlsx').Line
$result | Out-File 'c:\temp\output.csv'

Powershell variable in file name

Everyday a file is being saved with the current date in format testDDMMYYY.csv for an example test24112017.csv.
I need to open the current day's file with Powershell, however, I haven't found a way for it to work with variables.
#Date (24112017)
[String]$a=(Get-Date).ToShortDateString() | Foreach-Object {$_ -replace "\.", ""}
#Open the file called test24112017
[int] $S1=Get-Content "D:\test$($a).csv" -Tail 1 | Foreach-Object {$_ -replace "`"", ""} | ForEach-Object{$_.Split(",")[1]} | write-host
How can I get that variable working inside that path?
Do not use (Get-Date).ToShortDateString() | Foreach-Object {$_ -replace "\.", ""}, just use Get-Date -Format 'ddMMyyyy' to format the date the way you want :
Get-Content "D:\test$(Get-Date -Format 'ddMMyyyy').csv" -Tail 1
Formatting Dates and Times
Hmm aren't you over-complicating things a bit?
First of all,
(Get-Date).ToShortDateString() doesn't give me 24112017 but 24-11-2017.
So i'm guessing we have different regional settings, what part of the world are you from?
Have you printed out $a? What does it give you?
I would go for a different approach. Since your filename is literally always newer than the previous one (because it holds a date).
I would just sort on the Name and select the first one (aka the newest file).
$newestfile = Get-ChildItem c:\temp | Sort-Object Name -Desc | select * -First 1
Now go do whatever you want with your latest file.
get-content $newestfile.fullname