Powershell script Audit Remote Desktop users logon - powershell

I found a script that logs all users of RDS servers which works great;
Link here
However I want to make it specific for 1 user, not all users.
I really don't know powershell so need some help.
Param(
[array]$ServersToQuery = (hostname),
[datetime]$StartTime = "January 1, 1970"
)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = (get-date).adddays(-7)
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
[array]$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "."
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan

So, you say …
(I really don't know powershell so need some help.)
..., but point to a very advanced PowerShell script you want to use.
It is vital that you do not use anyone's code that you do not fully understand what it is doing from anyone. You could seriously damage / compromise your system(s) and or you entire enterprise. Please ramp up to protect yourself, your enterprise and avoid unnecessary confusion, complications, issues, errors and frustration you are going to encounter:
Follow this link
As for your query...
However I want to make it specific for 1 user, not all users.
… Though the script returns all users, you can just filter / prompt for the one user you are after, without changing anything about the authors code.
Prompt for a user by adding an additional parameter in that param block
[string]$targetUser = (Read-Host -Prompt 'Enter a username')
In that $FilteredOutput section, is where you'd use the additional $targetUser parameter, using the Where-Object cmdlet or string matching there or in the ….
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
… section. Something like...
($FilteredOutput -match $TargetUser) | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
I do not have an environment to test this, so, I'll leave that up to you.
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
This is all basic PowerShell 'using parameters' use case, and covered in all beginning PowerShell courses, books, websites, and built-in PowerShell help files.

Related

Create csv file of all disabled AD users with mailboxes Output information from multiple cmdlets in powershell

I am trying to gather some information on disabled user accounts that have mailboxes. I am specifically looking for just user mailboxes not shared mailboxes.
Here is what I have so far.
$Mailboxes = Get-Mailbox | where {$_.RecipientTypeDetails -eq 'UserMailbox'}
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #()
Foreach ($Mailbox in $Mailboxes) {
if((Get-ADUser -Identity $Mailbox.SamAccountName).Enabled -eq $False){
$Disabled += Get-MailboxStatistics $Mailbox.SamAccountName | Select -Property DisplayName,TotalItemSize
}
}
$Disabled | Sort DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Additionally what I would like to collect is the users Title, Manager, LastlogonDate all of which can be found using Get-Aduser. I am unsure how I go about collecting the information from both cmdlets and then exporting it all to csv. I have read that I may need to create a custom object. I am struggling with setting that up in this script.
Any help would be much appreciated.
Thanks
the following lines should give you what you want, can't verify it as I have no exchange running here.
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #(
Foreach ($Mailbox in $Mailboxes) {
$adUser = get-aduser -Identity $Mailbox.SamAccountName -Properties enabled,manager,title,lastlogontimestamp
If ($adUser.Enabled -eq $False){
$mailStats = Get-MailboxStatistics $Mailbox.SamAccountName
$attrsht = [ordered]#{
displayname=$mailstats.displayname
totalitemsize=$mailStats.totalitemsize
samaccountname=$aduser.samaccountname
enabled=$aduser.enabled
manager=$aduser.manager
title=$aduser.title
lastlogontimestamp=[datetime]::FromFileTime($aduser.lastlogontimestamp)
}
new-object -TypeName psobject -Property $attrsht
}
}
)
$Disabled | Sort-Object DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Avoid adding elements to an array by using +=. It is slow, alternatively take a look at generic array lists.

Read CSV and loop through in PowerShell

I am trying to Import a list of endpoints I have which is saved to a CSV, Loop through the list and Display the DeviceName, Owner, Depatment, Jobtitle. Please help.
$Endpoints = Import-Csv -Path "C:\Users\Ryan\Endpoints.csv"
$Result = #()
$Users = Get-AzureADDevice -filter $Endpoints.Endpoint | select DisplayName, objectID
$Users | ForEach -Object {
$user = $_
Get-AzureADDeviceRegisteredOwner -ObjectId $user.ObjectId -All $true |
ForEach -Object {
$Result += New-Object PSObject -property #{
DeviceOwner = $_.DisplayName
DeviceName = $user.Displayname
Department = $_.Department
Jobtitle = $_.Jobtitle
}
}
}
$Result | Select DeviceOwner, DeviceName, Department, Jobtitle
I had this working without Import using a static Endpoint Name e.g. $Users = Get-AzureADDevice -SearchString "LaptopName"
Based on assumptions this might be what you're looking for, note the outer loop to iterate over each row of your CSV:
Import-Csv -Path "C:\Users\Ryan\Endpoints Society\Desktop\Endpoint.csv" | ForEach-Object {
$devices = Get-AzureADDevice -SearchString $_.Endpoint
if(-not $devices) { return "$($_.EndPoint) not found, skipping" }
foreach($device in $devices) {
foreach($owner in Get-AzureADDeviceRegisteredOwner -ObjectId $device.ObjectId -All $true) {
[pscustomobject]#{
DeviceOwner = $owner.DisplayName
DeviceName = $device.Displayname
Department = $owner.Department
Jobtitle = $owner.Jobtitle
}
}
}
}
Not your answer, but how to find it
This script suffers greatly from 'one-liner-itis'. It is very hard to follow and going to be impossible to debug because of its over-the-top reliance on pipes and foreach-object commands§
Step one to make this even testable is to unwrap these commands into stand-alone commands, then you can debug line by line and see what the values are at that line.
$Endpoints = Import-Csv -Path "C:\Users\Ryan\Endpoints.csv"
$Result=#()
$devices= Get-AzureADDevice -filter $Endpoints.Endpoint |
select DisplayName, objectID
#for debugging, we will only look at the first two devices
$devices = $devices[0..1]
foreach($device in $devices){
$thisDeviceOwners = $_Get-AzureADDeviceRegisteredOwner -ObjectId $user.ObjectId -All $true
foreach($owner in $thisDeviceOwners){
$Result += New-Object PSObject -property #{
DeviceOwner = $_.DisplayName
DeviceName = $user.Displayname
Department = $_.Department
Jobtitle = $_.Jobtitle
}
}
}
$Result | Select DeviceOwner, DeviceName, Department, Jobtitle
Next, just run this one time in your IDE of choice, like PowerShell ISE or Visual Studio Code, then look to see what values you have for these variables. One of them will be empty or odd, and therein lies your problem:
$device
$devices
$thisDeviceOwners
$owner
Note that I changed $devices to only be the first two items in $devices, so you can see how the code is working. Find and fix your problem with one or two items and then the whole script will likely work.
§ - there is nothing wrong with your approach, and in fact PowerShell was built from the ground up to be like the Unix Bash Shell, loads of small commands that do one thing, and can be used to pass results on to the next command. It allows for ad-hoc powerful control.
But in an enterprise script, we should not use one-liners and should instead use commonly accepted coding practices. Each line of code should do only one thing, and shouldn't wrap around.

How to pipe different output when using "Restart-Computer" powershell

I have a csv file
SERVERS|SEQUENCE|INDEX
ServerA| 1 | 1.1
ServerB| 1 | 2.1
ServerC| 2 | 3.1
And here is my Code
#importing csv into ps$
$csv = Import-Csv "sequencing.csv"
#Grouping & Sort data from csv
$objdata = $csv | Select SERVERS,SEQUENCE | Group SEQUENCE | Select #{n="SEQUENCE";e={$_.Name}},#{n="SERVERS";e={$_.Group | ForEach{$_.SERVERS}}} | Sort SEQUENCE
foreach ($d in $objdata)
{
$order = $d.SEQUENCE
$cNames = $d.SERVERS
if (Restart-Computer -ComputerName $cNames -Force -Wait -For Wmi -Delay 1 -ErrorAction SilentlyContinue)
{
write-host "$cNames is rebooting" -ForegroundColor Green
}
else
{
Write-Host "Unable to reboot $cNames remotely, Please do it manually" -ForegroundColor Red
}
}
I am trying to reboot multiple servers in sequence and piping the output through 2 different results.
From my code, all the servers will reboot but will output through the else statement.
Can anyone point me to the right direction?
Any help would be appreciated.
As Lee_Daily already pointed out, the Restart-Computer does not return anything. That means you will have to use a try/catch block.
To make sure that in the event of an error you actually enter the catch block, you need to set the ErrorAction parameter to Stop.
In your code, you show a csv to import that has the piping symbol | as delimiter, so you need to specify that on the Import-Csv cmdlet.
From your latest comment, I gather that you want to loop one computer at a time, instead of restarting multiple computers at once, so you will know which computer errors out.
Try:
# importing csv into ps$
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|'
# Grouping & Sort data from csv
$objdata = $csv | Select-Object SERVERS,SEQUENCE |
Group-Object SEQUENCE |
Select-Object #{Name = "SEQUENCE"; Expression = {$_.Name}},
#{Name = "SERVERS"; Expression = {$_.Group.SERVERS}} |
Sort-Object SEQUENCE
$objdata | ForEach-Object {
foreach ($server in $_.SERVERS) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}
}
Seeing your latest comment, I understand that you only want to reboot servers where the SEQUENCE value in the CSV is set to '1'. Correct?
Also, you want to restart the servers at the same time, but also want to be able to see what server did not restart. Maybe you can do that with running Restart-Computer -AsJob, but I believe that only works in PowerShell 5.1
Below code filters out all servers with SEQUENCE set to 1 and restarts them one at a time (as requested in your original question "I am trying to reboot multiple servers in sequence"):
# import csv data
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|' # change this to the delimiter actually used in the CSV !
# get the list of servers where SEQUENCE is set to '1'
$servers = $csv | Select-Object SERVERS,SEQUENCE |
Where-Object { $_.SEQUENCE.Trim() -eq '1' } |
ForEach-Object { $_.SERVERS.Trim() }
foreach ($server in $servers) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}

Trigger an email alert in PowerShell when ExpiryInDays data returned by PSCustomObject is less than or equal to 30

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.

Search and find data from CSV based on Criteria

I am creating powershell script to create skype for business user account.
I am trying to find the avaialble number from CSV file which looks for Status, Business Unit and location to find correct LineUri to be used.
I am using following code which always use the first SPARE number and doesn't validate the location and business unit to find the line uri.
$path = Split-Path -parent $MyInvocation.MyCommand.Definition
$newpath = $path + "\PhoneData.csv"
$LineUri = #()
$Status = #()
$BusinessUnit = #()
$Location = #()
$csv = Import-Csv $newpath -Delimiter ","
$csv |`
ForEach-Object {
$LineUri += $_.LineUri
$Status += $_.Status
$BusinessUnit +=$_.BusinessUnit
$Location +=$_.Location
}
$currentStatus = "Spare"
$userBusinessUnit = "Support"
$userLocation = "WA"
if ($Status -eq $currentStatus -And $BusinessUnit -eq $userBusinessUnit -And $Location -eq $userLocation )
{
$Where = [array]::IndexOf($Status, $currentStatus)
$AvailableURI = $LineUri[$Where]
#Write-Host "Next Uri Available: " $LineUri[$Where]
$changeWhere = [array]::IndexOf($LineUri, $AvailableURI)
#Write-Host "Next Uri Available: " $Status[$changeWhere]
Try
{
Enable-CsUser -identity sudip.sapkota -RegistrarPool "s4b-fe01.tapes.com" -SipAddressType SamAccountName -sipdomain "tapes.com"
Set-CsUser -Identity sudip.sapkota -EnterpriseVoiceEnabled $true -LineURI $AvailableURI
Grant-CsDialPlan -Identity sudip.sapkota -PolicyName 'DialPlan'
Grant-CsVoicePolicy -Identity sudip.sapkota -PolicyName 'My VoicePolicy'
Write-Host "[INFO]`t Lync Enterprise account for user sudip.sapkota has been created with sip : $AvailableURI" -ForegroundColor "Green"
"[INFO]`t Lync Enterprise account for user sudip.sapkota has been created with sip : $AvailableURI" | Out-File $log -append
$i = $changeWhere
$csv[$i].Status = 'Used'
$csv | Export-Csv -Path $newpath -NoTypeInformation
Write-Host "[INFO]`t PhoneData CSV has been updated" -ForegroundColor "Green"
"[INFO]`t PhoneData CSV has been updated" | Out-File $log -append
}
catch
{
Write-Host "[ERROR]`t Oops, something went wrong: $($_.Exception.Message)`r`n" -ForegroundColor "Red"
"[WARNING]`t Oops, something went wrong: $($_.Exception.Message)" | Out-File $log -append
}
}
else
{
Enable-CsUser -identity sudip.sapkota -RegistrarPool "s4b-fe01.tapes.net" -SipAddressType SamAccountName -sipdomain "tapes.net"
Write-Host "[INFO]`t No Spare LineURI, user has been created as PC-to-PC only" -ForegroundColor "Yellow"
"[INFO]`t No Spare LineURI, user has been created as PC-to-PC only" | Out-File $log -append
}
My CSV looks like this.
Name LineUri Status BusinessUnit Location
Test 1 tel:+61396176100;ext=6100 Spare Sales VIC
Test 2 tel:+61396176101;ext=6101 Spare Sales VIC
Test 2 tel:+61396176102;ext=6102 Used Sales NSW
Test 2 tel:+61396176103;ext=6103 Spare Support WA
Test 2 tel:+61396176104;ext=6104 Spare Support WA
Test 2 tel:+61396176105;ext=6105 Used Action VIC
Test 2 tel:+61396176106;ext=6106 Spare Suppot VIC
Test 2 tel:+61396176107;ext=6107 Spare Action VIC
Can someone help to find my mistake?
As I am manually feeding the input, the test input are
$currentStatus = "Spare"
$userBusinessUnit = "Support"
$userLocation = "WA"
So I need to find the LineURI which is SPARE, whose location is WA and whose BusinessUnit is Support.
I should get tel:+61396176103;ext=6103 as LineURI
I reworked your logic a bit. I felt that you could do all of this inside the foreach loop. For clarity, this does gather the data in the loop and then leaves you with an array to work with. You could just nest your actions inside the loop directly and break out or you could keep it like this and just execute your work against the array item after.
# Array of spare numbers that meet your criteria
$firstAvailableSpare
# criteria
$currentStatus = "Spare"
$userBusinessUnit = "Support"
$userLocation = "WA"
$csv |`
ForEach-Object {
$LineUri += $_.LineUri
$Status += $_.Status
$BusinessUnit +=$_.BusinessUnit
$Location +=$_.Location
$currentStatus = "Spare"
$userBusinessUnit = "Support"
$userLocation = "WA"
if ( ($_.Status -eq $currentStatus) -And ($_.BusinessUnit -eq $userBusinessUnit) -And ($_.Location -eq $userLocation) ) {
$firstAvailableSpare = $_ # Assign the first
$_.Status = "Used"
continue # break the loop so we don't get additional hits
}
}
$csv | Export-Csv -Path $newpath -NoTypeInformation
$firstAvailableSpare # <-- your first available spare
$firstAvailableSpare.LineUri # <-- the URI you want
edit 1: Updated for CSV export requirement
edit 2: Changed break to continue. WARNING: For some reason this breaks the ISE's debugging functionality. Stepping will cease and future break points will be ignored. I don't know why this is but it is out of scope for your goal.