I am trying to export a list of guest user's last sign-in time to a CSV file via PowerShell.
Atfirst, I used Connect-AzureAD to get into Azure AD from PowerShell.
I found the below command where I can fetch only the guest users list.
Get-AzureADUser -Filter "UserType eq 'Guest' and AccountEnabled eq true"
Now, from the above list, I want to retrieve the last sign-in time property along with their displayname or UPN.
I found below link that is partially similar my scenario:
https://learn.microsoft.com/en-us/answers/questions/231133/azure-guest-user-account-last-signin-details.html
In the above link, they are checking whether the guest user has logged in for the last 30 days or not. But, I want to retrieve the last signInTime of all guest users.
Is this possible? Has anyone tried something like this and achieved it??
I tried in my environment and got last sign-in time of all guest users successfully by using the below PowerShell Script:
$guests = Get-AzureADUser -Filter "userType eq 'Guest'" -All $true
foreach ($guest in $guests) {
$Userlogs = Get-AzureADAuditSignInLogs -Filter "userprincipalname eq `'$($guest.mail)'" -ALL:$true
if ($Userlogs -is [array]) {
$timestamp = $Userlogs[0].createddatetime
}
else {
$timestamp = $Userlogs.createddatetime
}
$Info = [PSCustomObject]#{
Name = $guest.DisplayName
UserType = $guest.UserType
LastSignin = $timestamp
}
$Info | Export-csv C:\GuestUserLastSignins.csv -NoTypeInformation -Append
Remove-Variable Info
}
Write-Host -ForegroundColor Green "Exported Logs successfully"
Output:
After running the above script, csv file generated like below:
Related
I have written up a script that gives me the ability to delete multiple user profiles and the registry keys associated off of a computer. this is for work, multiple people share computers at a few clinics of ours and eventually the computer gets filled up of user profiles of people that no longer work there or just got their own computer. so the storage ends up running out, thats where i come in, deleting a lot of the user profiles to clean up the disk. i wanted to make this a lot easier so i tried writing a script. here it is. btw this is all domain join. this can be done by group policy i'm aware but my engineers haven't made that happen, i'm just help desk trying to make my life easier.
$profiles = Get-CimInstance -Class Win32_UserProfile
$users = 'schaudhary'
foreach ($profile in $profiles){
if ($profile.Special -ne 'True'){
if ($profile.LocalPath.split('\')[-1] -notcontains $users) {
$profile | Where-Object { $_.LocalPath.split('\')[-1] } | Remove-CimInstance -WhatIf
Write-Host "deleting" $profile.LocalPath
}
}
}
problem is when i try to exclude multiple users from the deletion process, it doesn't work. but when i have just one user, like right now "schaudhary" it'll work (it'll exclude schaudhary). how can i make it exclude multiple users?? I have to exclude the local admin acct, the active users on the machine and some special service accounts. and if anyone can give tips on adding last use time included in here that'll help. so only delete if user is 90 days old or more, something like that.
an event associated with a user would mean that he was working on a computer.
But you have to make sure there are logs for 90 days
$sid =(Get-adUser $username).sid.value
$StartDate = (Get-Date) - (New-TimeSpan -Day 90)
Get-WinEvent -FilterHashtable #{LogName='Security';data=$sid;StartTime=$StartDate} -MaxEvents 1
You are reversing the variables when using -notcontains..
Its use is $collectionOfThings -contains $singleThing or
$collectionOfThings -notcontains $singleThing.
Since PowerShell version 3, you can also do the reverse using -in and -notin as in
$singleThing -in $collectionOfThings or the negation of that
$singleThing -notin $collectionOfThings.
See Containment operators
Try
$profiles = Get-CimInstance -Class Win32_UserProfile
$users = #('schaudhary') # array of profiles to keep
foreach ($profile in $profiles){
if (!$profile.Special) {
$userName = $profile.LocalPath.split('\')[-1]
# or:
# if ($userName -notin $users) {
if ($users -notcontains $userName) {
if (!$profile.Loaded) {
$profile | Remove-CimInstance -Confirm:$false -WhatIf
Write-Host "Deleting $($profile.LocalPath)"
}
else {
Write-Warning "Profile $($profile.LocalPath) is currently loaded.."
}
}
}
}
Occasionally I forget to log off from a server or am disconnected through an error and I don't remember the name of the server. And my domain account starts getting periodically locked out, so I have to access logs on DC to find out which server(s) keep locking my account and log off from it/them. So I wanted to write to script in powershell that would log me off from all servers in a domain (with the exception of the server where I run the script on of course) without me needing to search which to log off from. This is what I have:
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
$SessionIDs | ForEach-Object
{
Write-Host "Logging off session [$($_)]..."
logoff $_
}
}
}
foreach ($Server in $Servers)
{
if ($Server -isnot $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock {$ScriptBlock}
}
}
But when I launch the script, nothing happens. The script doesn't return any errors but doesn't log me off from any server, nor does it write any of the messages from Write-Host cmdlet, obviously. I noticed the $SessionIDs variable definition only returns ID of the first session. Usually this shouldn't be a problem, since it's unlikely I will have more than one session on a server, but I'd like to have this insurance. Can anyone tell me what's wrong in the script?
I notice a few things...
"First, I don't think quser | Where-Object {$_ -match $env:USERNAME} will ever return anything. The output of quser will not contain the hostname."
Try this for getting logon sessions:
$Sessions = (query user /server:$Env:ComputerName) -split "\n" -replace '\s\s+', ';' |
ConvertFrom-Csv -Delimiter ';'
Next, when you reference the $Server variable on the remote machine in your script block, it is out of scope. You would need to use $Using:Server in the script block.
Lastly, the -isnot operator doesn't compare value, it compares type. So in your last foreach, the if statement evaluates to "if type string is not type string" and will not run. Try -ne or -notlike instead.
Working with objects is much easier if you can just parse the output of QUser.exe. Given your scenario, here's my take on it:
$servers = (Get-ADComputer -Filter '*').Name.Where{$_ -ne $env:COMPUTERNAME}
foreach ($server in $servers)
{
if (-not ($quser = ((QUser.exe /server:$server) -replace '\s{20,39}',',,' -replace '\s{2,}',',' 2>&1) | Where-Object -FilterScript { $_ -match $env:USERNAME })) {
Continue
}
Write-Verbose -Message "$($quser.Count) session(s) found on $server." -Verbose
($quser.Trim() | ConvertFrom-Csv -Header 'USERNAME','SESSIONNAME','ID','STATE','IDLE TIME','LOGON TIME').foreach{
Write-Verbose -Message "Logging user [$($_.UserName)] off." -Verbose
LogOff.exe $_.ID /server:$server
}
}
Filtering should always happen before hand meaning, filter out your computer name on your first call to Get-ADComputer. Since you're using QUser.exe and LogOff.exe to begin with, I'd recommend the use of it all the way through since LogOff accepts an ID value that QUser outputs.
Next, placing the call to quser inside your if statement does two things in this case.
Filters for all users matching $ENV:UserName
Returns $true if anything is found, and $false if not found.
So, switching the results using -not will turn $false into $true allowing the execution of the code block which will just continue to the next server.
This in turn doesn't bother with the rest of the code and continues onto the next computer if no matching names were found.
The use of $quser inside the if statement is so you can save the results to it if more than one name is found; (..) allows this as it turns the variable assignment into an expression having the output pass through onto the pipeline where it is either empty, or not.
Finally, referencing the $quser variable we can convert the strings into objects piping to ConvertFrom-Csv. Only step left to do is iterate through each row and passing it over to LogOff to perform the actual logoff.
If you've noticed, the headers are manually-specified because it is filtered out by the Where-Object cmdlet. This is a better approach seeing as there could be "more than one" RDP Session, now you're just left with those sessions matching the name which can be saved to $quser, so no extra filtering is needed down the line.
So I modified the script this way and it works, sort of. It logs off account from servers, which is the main goal. There are still some glitches, like the message it sends from the first Write-Host doesn't give server's name, the message from second one gives a different value than it should (it gives [1] value after -split instead of [2] for some reason; but those are not really that important things, even though I will try to make at least the first message right) and $SessionIDs still gives only the first value, but usually you shouldn't have more than one RDP session per server. I've seen more sessions of one user, but that is very rare. But I'd also like to fix this if possible. Nevertheless, the script basically does the most important thing. But if someone has a suggestion how to fix the glitches I mentioned I would be grateful.
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = , ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
Foreach ($SessionID in $SessionIDs)
{
Write-Host "Logging off session $SessionID..."
logoff $SessionID
}
}
}
foreach ($Server in $Servers)
{
if ($Server -ne $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock $ScriptBlock
}
}
How to get AD-group users list from LDAP using PowerShell without username and password.
Get-ADGroup -LDAPFilter (&(objectCategory=group)((cn=Testgrp"))))
I am trying this way but not fixing can anyone please help me out?
Right now I'm able to get the AD-Group info by using the below PowerShell scripts.
Get the group Info:
Get- ADGroupMember -Identify TEST_GRP_NM | select distinguishName | ft
Get-AdUser -filter{Name -like "GROUP_NM"} -Properties *
Get the user info:
Get-AdUser -Server "DOMAIN" -Identify "NTID" -Properties MemberOf
Note: Need to achieve the list of users from the LDAP group without using LDAP username and password
I personnally use this script to crawl through the AD (from another StackOverFlow question)
In case it becomes somehow a broken link:
# Your filter
$Filter = "(&(objectCategory=group)((cn=Testgrp))))"
# The path you want to scan
$RootOU = "OU=AnotherOU,OU=AnOU,DC=etc,DC=Something"
# The scope Base, One-level or Subtree
# The name is explicit enough
$Scope = "subtree"
# Instanciation and configuration of the directory searcher
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($RootOU)")
$Searcher.Filter = $Filter
$Searcher.SearchScope = $Scope
# Getting results from the AD
# A first pipe to get the member property returning a list of member
# A second pipe to display each member of the list in a line
$Searcher.FindAll() | Foreach-Object {$($_.Properties["member"])} | Foreach-Object {"$($_)`n"}
Hope it helps !
Iam trying to create a task that will executed later with tasksch on the local machine, that will run everyday.
The task is, if a user has not logged in to the computer, it shall be removed from the computer.
The Thing is there is something wrong with my code and i dont know what
For now iam trying that the script shall remove testuser 1,2,3 and on testuser 4 is shall say Account is not old by using lastusetime.
System-info
OS Name: Microsoft Windows 10 Enterprise
OS Version: 10.0.17763 N/A Build 17763
Account-Info
#{Sid=S-1-5-21-3824012622-276487612-2647460976-1105; LocalPath=C:\Users\testuser4; LastUseTime=2019-05-13 19:27:57}
#{Sid=S-1-5-21-3824012622-276487612-2647460976-1109; LocalPath=C:\Users\testuser3; LastUseTime=2019-05-10 14:54:07}
#{Sid=S-1-5-21-3824012622-276487612-2647460976-1108; LocalPath=C:\Users\testuser2; LastUseTime=2019-05-10 14:54:07}
#{Sid=S-1-5-21-3824012622-276487612-2647460976-1107; LocalPath=C:\Users\testuser1; LastUseTime=2019-05-10 13:49:16}
# Start PS-Code
$DeleteIfLastUseTimetIsOlderThen = (get-date).AddDays(-5).tostring("yyyy-MM-dd hh:mm:ss”)
$GetLocalAccounts = Get-WmiObject -ComputerName localhost -Filter "Special=False" -Class Win32_UserProfile |
Select-Object Sid, LocalPath, #{Label='LastUseTime';Expression={$_.ConvertToDateTime($_.LastUseTime)} }
foreach ($UserAccount in $GetLocalAccounts)
{
if ($GetLocalAccounts.LastUseTime -Ge $DeleteIfLastUseTimetIsOlderThen )
{ Write-host "Account is old, Remove me"}
Else
{ Write-host "Account is not old"}
}
# End PS-Code
The thing is that it doesn't matter if I change the value "$DeleteIfLastUseTimetIsOlderThen " to 1,2,3,4 or 55, everything seems to be old.
The issue here is twofold. One is that you want to use a less than comparison when looking for an older date since a date in the past is less than a date in the future. Second, you want to compare each date in your collection ($GetLocalAccounts) with a specific fixed date ($DeleteIfLastUseTimeIsOlderThan). To do this with your code structure, you will need to check against the current object ($UserAccount) in your for loop.
# Start PS-Code
$DeleteIfLastUseTimetIsOlderThan = (get-date).AddDays(-5).tostring("yyyy-MM-dd hh:mm:ss”)
$GetLocalAccounts = Get-WmiObject -ComputerName localhost -Filter "Special=False" -Class Win32_UserProfile |
Select-Object Sid, LocalPath, #{Label='LastUseTime';Expression={$_.ConvertToDateTime($_.LastUseTime)} }
foreach ($UserAccount in $GetLocalAccounts)
{
if ($UserAccount.LastUseTime -le $DeleteIfLastUseTimetIsOlderThan )
{ Write-host "Account is old, Remove me"}
Else
{ Write-host "Account is not old"}
}
# End PS-Code
I have a list of 150 computers I would like to disable in active directory with powershell.
So I have a csv file with the computernames and the follwoing script:
Import-Module ActiveDirectory
$computers = import-csv "C:\temp\test.csv"
foreach ($computer in $computers)
{
$computer | disable-adaccount
$computer | move-adobject -targetpath "OU=Disabled computers, DC=domain, DC=com"
}
Following error occures:
Disable-ADAccount : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeli
ne input.
Can someone please help?
Cheers
Try a regular parameter instead of a pipe: disable-adaccount $computer
(If necessary repeat this for the other call)
Also have a look at this question at Technet: Powershell script to disable AD account based on CSV file
Create a txt file called disable.txt and put list of computers that u want to disable on C:\temp location
Run this script:
$Computer = Get-content c:\temp\disable.txt
Foreach ($Computer in $computers) {
Set-ADComputer -Identity $computer -Enabled $false
}
I know this is an old post but I believe it's a type mismatch of Disable-ADAccount expecting a property rather than the whole object for input.
Using the example I added .DistinguishedName to the item passed to Disable-ADAccount and it worked. The input csv would need to have DistinguishedName available to the command though.
{
Disable-ADAccount $StaleComputer.DistinguishedName
Move-ADObject $StaleComputer.DistinguishedName -TargetPath "OU=Disabled Computers, DC=domain, DC=com"
}