Could someone please assist with this one.
My current code imports a csv file which has three columns, so far it will update the column names to be more readable. I need to add a fourth column which is a calculated field based on a datetime field.
So need to check the datetime field then display a number of days before it is 90 days old.
e.g. "Today's date" - "03/03/2020 8:00:00 AM" = 31 days
90 days - 31 days = 59 days (the 59 days is to go into the calculated field column
Bit of a newb with powershell and have all other functions working, but this is what I'm left with and need to add it into the below call, when the csv is imported, header columns updated then exported to a new file.
$input = "C:\Data\test\unchanged101.csv"
$output = "C:\Data\test\unchanged101conv.csv"
$checkDate = (Get-Date).AddDays(-90)
$data = Import-Csv $input |
Where-Object {
($_."pwdlastset" -as [DateTime]) -lt $CheckDate
}
$headerConversion = #(
#{ Name = 'User account'; Expression = { $_.'cn' } }
#{ Name = 'Last modified date'; Expression = { $_.'pwdlastset' } }
#{ Name = 'Email address'; Expression = { $_.'mail' } }
)
(Import-Csv -Path $input) |
Select-Object -Property $headerConversion | Select-Object *,"Days Left" |
Export-Csv -Path $output -NoTypeInformation
The new column is the "Days Left" where I need to display the number of days left until it is 90 days old. How to I get the result from the code here, into that column for each row?
$checkDate = (Get-Date).AddDays(-90)
$data = Import-Csv $input |
Where-Object {
($_."pwdlastset" -as [DateTime]) -lt $CheckDate
}
Been working on this one for the past few days and just cant figure the last part out.
First of all, you should not use variable name $input as this is an Automatic variable
If you subtract one datetime object from another, the result is a TimeSpan object which has a property called Days you could use
$inputFile = "C:\Data\test\unchanged101.csv"
$outputFile = "C:\Data\test\unchanged101conv.csv"
$checkDate = (Get-Date).AddDays(-90).Date # .Date sets this to midnight
$result = Import-Csv $inputFile |
Where-Object { [DateTime]$_.pwdlastset -lt $CheckDate } |
Select-Object #{ Name = 'User account'; Expression = { $_.cn } },
#{ Name = 'Last modified date'; Expression = { $_.pwdlastset } },
#{ Name = 'Email address'; Expression = { $_.mail } },
#{ Name = 'Days Left'; Expression = { ($checkDate - [DateTime]$_.pwdlastset).Days } }
# output on screen
$result | Format-Table -AutoSize
# output to csv
$result | Export-Csv -Path $outputFile -NoTypeInformation
I would first add the new column and then go trough all the lines (with a foreach loop) check the remaining days and write them to the new "Days Left" column.
Note: I omitted your header conversion here. You need to run it first and then the "Days Left" code...
$data = Import-Csv $input #import everything
foreach($line in $data){
$daysLeft = 0
$daysSinceLastSet = ((Get-Date) - [DateTime]$line.pwdlastset).Days
if ($daysSinceLastSet -lt 90){
$daysLeft = 90-$daysSinceLastSet
}
$line."Days Left" = $daysLeft
}
$data | Export-Csv -Path $output -NoTypeInformation
I have file with below data, I want to get the highest time difference from this using PowerShell.
STARTTIME:2018-12-01 04:13:15
ENDTIME:2018-12-01 04:17:15
--
--
STARTTIME:2018-12-01 04:11:15
ENDTIME:2018-12-01 04:13:15
--
STARTTIME:2018-12-01 04:10:15
ENDTIME:2018-12-01 04:10:40
I expect the output to be 00:04:00
Use a Regular Expression to select lines and STARTTIME/ENDTIME
to set a variable with that name and the [datetime] value
## Q:\Test\2019\05\15\SO_56150523.ps1
$FileIn = '.\times.txt'
$RE = [regex]'^(STARTTIME|ENDTIME):(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})'
$Data = (Get-Content $FileIn)| Where-Object {$_ -match $RE}| ForEach-Object{
Set-Variable -Name $Matches[1] -Value (Get-Date $Matches[2])
if ($Matches[1] -eq 'ENDTIME'){
[PSCustomObject]#{
StartTime = $StartTime
EndTime = $EndTime
Duration = New-TimeSpan -Start $StartTime -End $EndTime
}
}
}
$Data
"`nThe longest duration is {0}" -f ($Data|Sort-Object Duration -Descending)[0].Duration
Sample output based on the complete text from question in file times.txt
> Q:\Test\2019\05\15\SO_56150523.ps1
StartTime EndTime Duration
--------- ------- --------
2018-12-01 04:13:15 2018-12-01 04:17:15 00:04:00
2018-12-01 04:11:15 2018-12-01 04:13:15 00:02:00
2018-12-01 04:10:15 2018-12-01 04:10:40 00:00:25
The longest duration is 00:04:00
I'm getting information on monitors from WMI. There are properties called "WeekOfManufacture" and "YearOfManufacture". In this case it's:
Week : 24
Year : 2009
I'm trying to get the approximate date corresponding to that.
I'm doing this to get a really rough estimate:
(Get-Date -Date $YearOfManufacture/01/01).AddDays(7*$WeekOfManufacture)
But obviously the "week" doesn't necessarily always have 7 days.
What is the best approach?
The complete code I'm using right now is:
$Monitors = Get-WmiObject WmiMonitorID -Namespace root\wmi
foreach ($Monitor in $Monitors) {
$Manufacturer = ($Monitor.ManufacturerName -notmatch 0 | ForEach{[char]$_}) -join ""
$Code = ($Monitor.ProductCodeID -notmatch 0 | ForEach{[char]$_}) -join ""
$Name = ($Monitor.UserFriendlyName -notmatch 0 | ForEach{[char]$_}) -join ""
$Serial = ($Monitor.SerialNumberID -notmatch 0 | ForEach{[char]$_}) -join ""
$WeekOfManufacture = $Monitor.WeekOfManufacture
$YearOfManufacture = $Monitor.YearOfManufacture
$DateManufacture = (get-date -Date $YearOfManufacture/01/01).AddDays(7*$WeekOfManufacture)
"$Manufacturer - $Code - $Name - $Serial - $DateManufacture"
}
which returns something like (info obfuscated):
SAM - 1234 - SycMastr - XXXX - 06/18/2009 00:00:00
SAM - 1234 - SycMastr - XXXX - 09/10/2009 00:00:00
Answer from Technet works perfectly
(https://social.technet.microsoft.com/Forums/en-US/f65c80b0-f74f-4234-870c-c5ffe8d9b1ea/powershell-get-date-from-week-number-of-year?forum=ITCG)
:
Function FirstDateOfWeek
{
param([int]$year, [int]$weekOfYear)
$jan1 = [DateTime]"$year-01-01"
$daysOffset = ([DayOfWeek]::Thursday - $jan1.DayOfWeek)
$firstThursday = $jan1.AddDays($daysOffset)
$calendar = ([CultureInfo]::CurrentCulture).Calendar;
$firstWeek = $calendar.GetWeekOfYear($firstThursday, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)
$weekNum = $weekOfYear
if($firstweek -le 1) { $weekNum -= 1 }
$result = $firstThursday.AddDays($weekNum * 7)
return $result.AddDays(-3)
}
FirstDateOfWeek -year 2009 -weekOfYear 24
8 juin 2009 00:00:00
I have a csv input file like:
date;time;pc;state
25.01.2017;10:30:57;pc1;inactive
25.01.2017;10:35:57;pc2;active
25.01.2017;10:37:34;pc1;active
25.01.2017;10:38:35;pc3;inactive
25.01.2017;10:39:20;pc1;inactive
25.01.2017;10:42:10;pc2;inactive
25.01.2017;10:42:10;pc3;active
So, i need to show only last pc repetition with inactive state and show the difference between time and current time.
So result must be:
pc1 inactive from 10:39:20
pc2 inactive from 10:42:10
Do not know how to realize it in powershell. Need help :)
Here you go, at the moment it only outputs it via Write-Host but you should be able to rebuild it to do with the data what you want:
$csv = import-csv .\test.csv -Delimiter ";"
$pcs = $csv.pc | select -Unique
foreach($pc in $pcs) {
#get all entries for current pc and order by date/time, then select newest entry
$le = $csv | where {$_.pc -eq $pc} | sort date,time -Descending | select -First 1
#if last state is inactive
if($le.state -eq "inactive"){
$writedate = get-date -Date $le.date -Hour $le.time.Substring(0,2) -Minute $le.time.Substring(3,2) -Second $le.time.Substring(6,2)
$td = (get-date) - $writedate
write-host "time difference for $pc : $($td.Days) days $($td.Hours) hours, $($td.Minutes) minutes, $($td.Seconds) seconds"
}
}
If your time field is not allways in HH:MM:DD you will have to change the script to accomodate that
Two solutions, a short one taking the question literal:
$csv = import-csv .\SO_41941150.csv -Delimiter ";"
$csv|group pc|%{$_.group|select -last 1|? state -eq 'inactive'|%{"$($_.pc) inactive from $($_.time)"}}
With the exact output:
pc1 inactive from 10:39:20
pc2 inactive from 10:42:10
A longer one converting date and time to datetime using ::ParseExact and giving elapsed TotalSeconds/Minutes as a result.
$csv = import-csv .\SO_41941150.csv -Delimiter ";"
$csv | Group-Object pc | Foreach-Object {
$_.Group | Select-Object -last 1 | Where-Object state -eq 'inactive'|
ForEach-Object {
$DT = [datetime]::ParseExact("$($_.date) $($_.time)","dd.MM.yyyy HH:mm:ss",$null)
$Secs = [int]((get-date) - $DT).TotalSeconds
$mins = [int]((get-date) - $DT).TotalMinutes
"$($_.pc) inactive since $Mins minutes or $Secs seconds"
}
}
Output:
pc1 inactive since 9160 minutes or 549578 seconds
pc2 inactive since 9157 minutes or 549408 seconds
I currently have the following query in PowerShell:
query user /server:$server
Which returns output:
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
svc_chthost 2 Disc 1:05 8/16/2016 12:01 PM
myusername rdp-tcp 3 Active . 8/29/2016 11:29 AM
Currently, I'm using #(query user /server:$server).Count - 1 as a value to represent the number of users logged on (it's not pretty, I know). However now I would like to obtain information such as USERNAME, ID, and LOGON TIME to use in other parts of my script.
My question is surrounding an easier way to parse the information above, or maybe a better solution to my problem all together: Counting and gathering information related to logged on users.
I've found other solutions that seem to work better, but I'm sure there's got to be a simpler way to accomplish this task:
$ComputerName | Foreach-object {
$Computer = $_
try
{
$processinfo = #(Get-WmiObject -class win32_process -ComputerName $Computer -EA "Stop")
if ($processinfo)
{
$processinfo | Foreach-Object {$_.GetOwner().User} |
Where-Object {$_ -ne "NETWORK SERVICE" -and $_ -ne "LOCAL SERVICE" -and $_ -ne "SYSTEM"} |
Sort-Object -Unique |
ForEach-Object { New-Object psobject -Property #{Computer=$Computer;LoggedOn=$_} } |
Select-Object Computer,LoggedOn
}#If
}
catch
{
}
Old question, but it seems a workable solution:
(query user) -split "\n" -replace '\s\s+', ';' | convertfrom-csv -Delimiter ';'
This chunks the output into lines, as the answer above does, but then replaces more than one white space character (\s\s+) with a semi-colon, and then converts that output from csv using the semi-colon as a delimiter.
The reason for more than one white space is that the column headers have spaces in them (idle time, logon time), so with just one space it would try to interpret that as multiple columns. From the output of the command, it looks as if they always preserve at least 2 spaces between items anyway, and the logon time column also has spaces in the field.
Awesome references in the comments, and still open to more answers for this question as it should have an easier solution!
foreach ($s in $servers) #For Each Server
{
foreach($ServerLine in #(query user /server:$s) -split "\n") #Each Server Line
{
#USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
$Parsed_Server = $ServerLine -split '\s+'
$Parsed_Server[1] #USERNAME
$Parsed_Server[2] #SESSIONNAME
$Parsed_Server[3] #ID
$Parsed_Server[4] #STATE
$Parsed_Server[5] #IDLE TIME
$Parsed_Server[6] #LOGON TIME
}
}
This solution solves the problem for now, kind of sloppy.
For more in-depth solutions with more functionalities, check the comments on the original question :)
Function Get-QueryUser(){
Param([switch]$Json) # ALLOWS YOU TO RETURN A JSON OBJECT
$HT = #()
$Lines = #(query user).foreach({$(($_) -replace('\s{2,}',','))}) # REPLACES ALL OCCURENCES OF 2 OR MORE SPACES IN A ROW WITH A SINGLE COMMA
$header=$($Lines[0].split(',').trim()) # EXTRACTS THE FIRST ROW FOR ITS HEADER LINE
for($i=1;$i -lt $($Lines.Count);$i++){ # NOTE $i=1 TO SKIP THE HEADER LINE
$Res = "" | Select-Object $header # CREATES AN EMPTY PSCUSTOMOBJECT WITH PRE DEFINED FIELDS
$Line = $($Lines[$i].split(',')).foreach({ $_.trim().trim('>') }) # SPLITS AND THEN TRIMS ANOMALIES
if($Line.count -eq 5) { $Line = #($Line[0],"$($null)",$Line[1],$Line[2],$Line[3],$Line[4] ) } # ACCOUNTS FOR DISCONNECTED SCENARIO
for($x=0;$x -lt $($Line.count);$x++){
$Res.$($header[$x]) = $Line[$x] # DYNAMICALLY ADDS DATA TO $Res
}
$HT += $Res # APPENDS THE LINE OF DATA AS PSCUSTOMOBJECT TO AN ARRAY
Remove-Variable Res # DESTROYS THE LINE OF DATA BY REMOVING THE VARIABLE
}
if($Json) {
$JsonObj = [pscustomobject]#{ $($env:COMPUTERNAME)=$HT } | convertto-json # CREATES ROOT ELEMENT OF COMPUTERNAME AND ADDS THE COMPLETED ARRAY
Return $JsonObj
} else {
Return $HT
}
}
Get-QueryUser
or
Get-QueryUser -Json
For gathering information.
based on https://ss64.com/nt/query-user.html
$result = &quser
$result -replace '\s{2,}', ',' | ConvertFrom-Csv
My own column based take. I'm not sure how much the ID column can extend to the left. Not sure how wide the end is. This is turning out to be tricky. Maybe this way is better: Convert fixed width txt file to CSV / set-content or out-file -append?
# q.ps1
# USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
# js1111 rdp-tcp#20 136 Active . 6/20/2020 4:26 PM
# jx111 175 Disc . 6/23/2020 1:26 PM
# sm1111 rdp-tcp#126 17 Active . 6/23/2020 1:13 PM
#
# di111111 rdp-tcp#64 189 Active 33 7/1/2020 9:50 AM
# kp111 rdp-tcp#45 253 Active 1:07 7/1/2020 9:43 AM
#
#0, 1-22, 23-40, 41-45, 46-53, 54-64, 65-80/82
$q = quser 2>$null | select -skip 1
$q | foreach {
$result = $_ -match '.(.{22})(.{18})(.{5})(.{8})(.{11})(.{16,18})'
[pscustomobject] #{
USERNAME = $matches[1].trim()
SESSIONNAME = $matches[2].trim()
ID = [int]$matches[3].trim()
STATE = $matches[4].trim()
IdleTime = $matches[5].trim()
LogonTime = [datetime]$matches[6].trim()
}
if (! $matches) {$_}
}
Invoke-command example. This is good if you're using Guacamole.
$c = get-credential
icm comp1,comp2,comp3 q.ps1 -cr $c | ft
USERNAME SESSIONNAME ID STATE IdleTime LogonTime PSComputerName RunspaceId
-------- ----------- -- ----- -------- --------- -------------- ----------
js1 136 Disc . 6/20/2020 4:26:00 PM comp1 a8e670cd-4f31-4fd0-8cab-8aa11ee75a73
js2 137 Disc . 6/20/2020 4:26:00 PM comp2 a8e670cd-4f31-4fd0-8cab-8aa11ee75a74
js3 138 Disc . 6/20/2020 4:26:00 PM comp3 a8e670cd-4f31-4fd0-8cab-8aa11ee75a75
Here's another version. The number in the ID column can be at least 1 column before the header. I figure out where the line ends on every line. The Sessionname ends in 3 dots if it's too long, and at least 2 spaces are between each column. The column headers always start at the same place.
ID can be 4 digits. Tricky.
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
rwo rdp-sxs22010... 342 Active 48 2/8/2022 1:41 PM
ym326 rdp-sxs22062... 1012 Active 9 9/27/2022 3:42 PM
cw7 rdp-tcp#4 4 Active 11:16 9/26/2022 7:58 AM
# q2.ps1
$first = 1
quser 2>$null | ForEach-Object {
if ($first -eq 1) {
$userPos = $_.IndexOf("USERNAME")
$sessionPos = $_.IndexOf("SESSIONNAME") # max length 15
$idPos = $_.IndexOf("ID") - 4 # id is right justified
# $idPos = $_.IndexOf("SESSIONNAME") + 15
$statePos = $_.IndexOf("STATE") # max length 6
$idlePos = $_.IndexOf("IDLE TIME") - 2 # right justified too
$logonPos = $_.IndexOf("LOGON TIME")
$first = 0
}
else {
$user = $_.substring($userPos,$sessionPos-$userPos).Trim()
$session = $_.substring($sessionPos,$idPos-$sessionPos).Trim()
$id = [int]$_.substring($idPos,$statePos-$idPos).Trim()
$state = $_.substring($statePos,$idlePos-$statePos).Trim()
$idle = $_.substring($idlePos,$logonPos-$idlePos).Trim()
$logon = [datetime]$_.substring($logonPos,$_.length-$logonPos).Trim()
[pscustomobject]#{User = $user; Session = $session; ID = $id;
State = $state; Idle = $idle; Logon = $logon}
}
}
Output:
User Session ID State Idle Logon
---- ------- -- ----- ---- -----
rwo rdp-sxs22010... 342 Active 48 2/8/2022 1:41:00 PM
Edited: Looks like someone have already created a script that actually works pretty well: https://gallery.technet.microsoft.com/scriptcenter/Get-LoggedOnUser-Gathers-7cbe93ea
Cant believe after so many years there is still no native PowerShell for this.
I've touched up what Tyler Dickson has done and ensure the result comes back as PSCustomObject
$Servers = #("10.x.x.x", "10.y.y.y")
$Result = #()
foreach ($Server in $Servers) {
$Lines = #(query user /server:$s) -split "\n"
foreach($Line in $Lines) #Each Server Line
{
if ($Line -match "USERNAME\s+SESSIONNAME\s+ID\s+STATE\s+IDLE TIME\s+LOGON TIME") {
continue # If is the header then skip to next item in array
}
$Parsed_Server = $Line -split '\s+'
$Result += [PSCustomObject]#{
SERVER = $Server
USERNAME = $Parsed_Server[1]
SESSIONNAME = $Parsed_Server[2]
ID = $Parsed_Server[3]
STATE = $Parsed_Server[4]
IDLE_TIME = $Parsed_Server[5]
LOGON_TIME = $Parsed_Server[6]
}
}
}
$Result | Format-Table
Example output:
SERVER USERNAME SESSIONNAME ID STATE IDLE_TIME LOGON_TIME
------ -------- ----------- -- ----- --------- ----------
10.x.x.x user01 rdp-tcp#13 6 Active . 28/06/2020
10.x.x.x user02 rdp-tcp#35 11 Active 59 29/06/2020
10.y.y.y user03 rdp-tcp#38 12 Active . 29/06/2020
10.y.y.y user04 rdp-tcp#43 14 Active 5 29/06/2020
Unfortunately, no one that proposes solutions with replace method didn't notice that it will be a data collision if SESSIONNAME will empty (it will be when user disc)
So you will have SESSIONNAME contain ID, ID contain STATE etc.
It's not good.
So I`ve fixed it by -replace 'rdp-tcp#\d{1,3}' and propose to you solution with headers.
$Header = "UserName", "ID", "State", "Idle", "Logon", "Time"
$Result = $(quser) -replace 'rdp-tcp#\d{1,3}' -replace "^[\s>]", "" -replace "\s+", "," | ConvertFrom-Csv -Header $Header
Now you can access to any object $Result.Username, $Result.Idle
Was looking for the easy solution to the query user problem that also addresses the issue when SessionName is blank. Ended up combining bits and pieces from the above and came up with this. This isn't perfect, but it does seem to work better than most.
$q = (query user) -split "\n" -replace '\s{18}\s+', " blank "
$qasobject = $q -split "\n" -replace '\s\s+', "," | convertfrom-csv
The First pass with -split will replace any chunk of 18 or more spaces with " blank ", NOTE; there are 2 spaces before and after blank.
The second pass with -split will replace anything with 2 or more spaces with a ",", then pass that through convertfrom-csv to make it an object.
If you want a quick solution and don't need all information, you can also do this:
$a = Get-CimInstance -ClassName Win32_UserProfile -ComputerName "Server-1" | where {$_.Loaded -and $_.LocalPath.split('\')[1] -eq "Users" -and $_.Special -eq $false}
$a | ft -a #{N='Name';E={$_.LocalPath.split('\')[2]}},LastUseTime,Loaded
I Further appended the above code to properly format and also consider the Disconnected users
$HaSH = #()
foreach($ServerLine in #(query user) -split "\n") {
$Report = "" | Select-Object UserName, Session, ID, State, IdleTime, LogonTime
$Parsed_Server = $ServerLine -split '\s+'
if($Parsed_Server -like "USERNAME*") {
Continue
}
$Report.UserName = $Parsed_Server[1]
$Report.Session = $Parsed_Server[2]
$Report.ID = $Parsed_Server[3]
$Report.State = $Parsed_Server[4]
$Report.IdleTime = $Parsed_Server[5]
$Report.LogonTime = $Parsed_Server[6]+" " +$Parsed_Server[7]+" "+$Parsed_Server[8]
if($Parsed_Server[3] -eq "Disc") {
$Report.Session = "None"
$Report.ID = $Parsed_Server[2]
$Report.State = $Parsed_Server[3]
$Report.IdleTime = $Parsed_Server[4]
$Report.LogonTime = $Parsed_Server[5]+" " +$Parsed_Server[6]+" "+$Parsed_Server[7]
}
if($Parsed_Server -like ">*") {
$Parsed_Server=$Parsed_Server.Replace(">","")
$Report.UserName = $Parsed_Server[0]
$Report.Session = $Parsed_Server[1]
$Report.ID = $Parsed_Server[2]
$Report.State = $Parsed_Server[3]
$Report.IdleTime = $Parsed_Server[4]
$Report.LogonTime = $Parsed_Server[5]+" " +$Parsed_Server[6]+" "+$Parsed_Server[7]
}
$HaSH+=$Report
}
$result = (&quser) -replace '\s{2,}', ',' | ConvertFrom-Csv | Select -ExpandProperty USERNAME
$loggedinuser = $result.Trim(">")