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
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
}
}
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:
Is it possible to get a list of installed software of a remote computer ?
I know to do this for a local computer with use of Powershell. Is it possible with Powershell to get installed software of a remote computer and save this list on the remote computer ?
This I use for local computers:
Get-WmiObject -Class Win32_Product | Select-Object -Property Name
Thanks in advance,
Best regards,
This uses Microsoft.Win32.RegistryKey to check the SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall registry key on remote computers.
https://github.com/gangstanthony/PowerShell/blob/master/Get-InstalledApps.ps1
*edit: pasting code for reference
function Get-InstalledApps {
param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[string]$NameRegex = ''
)
foreach ($comp in $ComputerName) {
$keys = '','\Wow6432Node'
foreach ($key in $keys) {
try {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
$apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
} catch {
continue
}
foreach ($app in $apps) {
$program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
$name = $program.GetValue('DisplayName')
if ($name -and $name -match $NameRegex) {
[pscustomobject]#{
ComputerName = $comp
DisplayName = $name
DisplayVersion = $program.GetValue('DisplayVersion')
Publisher = $program.GetValue('Publisher')
InstallDate = $program.GetValue('InstallDate')
UninstallString = $program.GetValue('UninstallString')
Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
Path = $program.name
}
}
}
}
}
}
There are multiple ways how to get the list of installed software on a remote computer:
Running WMI query on ROOT\CIMV2 namespace:
Start WMI Explorer or any other tool which can run WMI queries.
Run WMI query "SELECT * FROM Win32_Product"
Using wmic command-line interface:
Press WIN+R
Type "wmic", press Enter
In wmic command prompt type "/node:RemoteComputerName product"
Using Powershell script:
Thru WMI object: Get-WmiObject -Class Win32_Product -Computer RemoteComputerName
thru Registry: Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize
thru Get-RemoteProgram cmdlet: Get-RemoteProgram -ComputerName RemoteComputerName
Source: https://www.action1.com/kb/list_of_installed_software_on_remote_computer.html
Here is an article detailing how to query the list of installed programs locally and remotely.
Also, it will interest you to know the reason why the Get-WmiObject cmdlet is working anymore. It has been superseded with the CimInstance cmdlet.
https://techdirectarchive.com/2020/12/22/how-to-get-a-list-of-the-installed-program-using-powershell-in-windows-10/
When working with the CimInstance cmdlet and you encounter this error "WinRM cannot complete the operation, verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled", I have described the steps to have it resolved in this link: WinRM cannot complete the operation, verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled.
https://techdirectarchive.com/2023/02/03/winrm-cannot-complete-the-operation-verify-that-the-specified-computer-name-is-valid-that-the-computer-is-accessible-over-the-network/
No one seems to know about get-package in powershell 5.1. You'll have to use invoke-command to run it on a remote computer.
get-package | more
Name Version Source ProviderName
---- ------- ------ ------------
7-Zip 21.07 (x64) 21.07 Programs
Wolfram Extras 11.0 (5570611) 11.0.0 Programs
ArcGIS Desktop Background G... 10.8.12790 Programs
# and so on...
I am trying to find a way of retrieving the date/time of which the last windows update was either installed, or checked for.
So far I have found a function that allows to list recent Windows Updates, but it is far too much data and too bloated for such a simple function. Secondly I have tried to access the registry although I am having no luck in retriving the value I am after.
I am testing this on a Windows 10 Machine although the software will probably reside on Windows Server 2012 R2.
Here is an example of some of the code I have tried:
$key = “SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install”
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$RemoteBase = [Microsoft.Win32.RegistryKey]::OpenBaseKey($keytype,"My Machine")
$regKey = $RemoteBase.OpenSubKey($key)
$KeyValue = $regkey.GetValue(”LastSuccessTime”)
$System = (Get-Date -Format "yyyy-MM-dd hh:mm:ss")
Also, just trying the Get-ChildItem
$hello = Get-ChildItem -Path “hkcu:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\”
foreach ($a in $hello) {
$a
}
I've checked in regedit and this key does not exist. Going to the "Windows Update" path shows only App Updates and not Windows updates.
EDIT
I seem to be closer to my goal with this line:
Get-HotFix | Where {$_.InstallDate -gt 30}
However how to I only retrive those of which have been installed in the last 30 days? And this doesnt show many results, even using Select $_.InstallDate
an option :
gwmi win32_quickfixengineering |sort installedon -desc
Another alternative, using the com object Microsoft.Update.Session can be find here : https://p0w3rsh3ll.wordpress.com/2012/10/25/getting-windows-updates-installation-history/
in short :
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa386532%28v=vs.85%29.aspx
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object {$_}
Here you have how to know the date and time of the last Windows update in a single line of Powershell:
(New-Object -com "Microsoft.Update.AutoUpdate"). Results | fl
You also have the following script to check it massively in Windows Server:
$ servers = Get-ADComputer -Filter {(OperatingSystem-like "* windows * server *") -and (Enabled -eq "True")} -Properties OperatingSystem | Sort Name | select -Unique Name
foreach ($ server in $ servers) {
write-host $ server.Name
Invoke-Command -ComputerName $ server.Name -ScriptBlock {
(New-Object -com "Microsoft.Update.AutoUpdate"). Results}
}
Extracted from: https://www.sysadmit.com/2019/03/windows-update-ver-fecha-powershell.html
Get-HotFix |?{$_.InstalledOn -gt ((Get-Date).AddDays(-30))}
Using PowerShell, you can get the date of the las Windows update like this:
$lastWindowsUpdate = (Get-Hotfix | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1).InstalledOn