Delete a list of User Profiles - powershell

(I am a newbie to PS who has mostly used VBS and Batch so I am still working on following PS scripts)
I need to delete most (but not all) domain accounts off of all 500 of our systems.
Some of these are from a specific list.
Some follow a generic format *21, *19, etc...
I can find scripts that will let me delete a specific user account but I can't figure out how to pass it the long list or use the wildcards...
This one seems promising if I can figure out how to get the values need in it...
:: This Script taken from https://www.nextofwindows.com/delete-user-profiles-on-a-remote-computer-in-powershell
$Computer = Read-Host "Please Enter Computer Name: "
$user = Read-Host "Enter User ID: "
Invoke-Command -ComputerName $computer -ScriptBlock {
param($user)
$localpath = 'c:\users\' + $user
Get-WmiObject -Class Win32_UserProfile | Where-Object {$_.LocalPath -eq $localpath} |
Remove-WmiObject
} -ArgumentList $user

It sounds like you are most of the way there. Just use some pipelines and a foreach loop or two.
This will attempt to remove all users in the list from all computers in the list:
# define function that takes user list from pipeline
function Remove-User {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true,Mandatory=$true)]
[string]
$user,
[Parameter(Mandatory=$true)]
[string]
$computer
)
process {
# copied from question
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class Win32_UserProfile |
Where-Object { $_.LocalPath -eq "c:\users\${$user}" } |
Remove-WmiObject
} -ArgumentList $user
}
}
# get your lists whatever way makes sense
$userList = Import-Csv -Path "users.csv" -Delimiter ','
$computerList = Import-Csv -Path "computers.csv" -Delimiter ','
# call function to remove all users for each computer
$computerList | ForEach-Object {
$userList | Remove-User -computer $_
}
I'm not sure where you get your list from, but I used a csv just because.
*note: This is going off the assumption that the Invoke-Command portion of code from nextOfWindows does what it says

Related

Store Each Line of PowerShell Output as same variable

So, I'm trying to remotely remove some manually added printer from a separate machine. Problem is, I don't want to remove all of the printers, only ones that have "HJK" or "LSG" anywhere in the name.
So, to get the list of names, I do:
Invoke-Command AaronsComputer -ScriptBlock {get-printer | select name -expandproperty name}
and to delete the printer, I would do:
Invoke-Command AaronsComputer -ScriptBlock {Remove-Printer -Name "Full Printer Name Here"}
I know I can export the results of get-printer as a CSV to work with it better but I'd rather not do that and have it all happen in Powershell.
I thought something along these lines would work but I don't think PowerShell intelligently sees each line as a variable.
$PrinterResults = Invoke-Command $Computer -ScriptBlock {get-printer | select name -expandproperty name}
foreach $PrinterResults
if ($PrinterResults -contains "HJK") {
Invoke-Command $Computer -ScriptBlock {Remove-Printer -Name "$PrinterResults"}
}
if ($PrinterResults -contains "LSG") {
Invoke-Command $Computer -ScriptBlock {Remove-Printer -Name "$PrinterResults"}
}
The end goal is that I can delete all printers that match the criteria in one go.
Managed to get this done in the end with the below:
Invoke-Command $Computer -ScriptBlock {
$Printers = get-printer | Where-Object {($_.name -like '*HJK*') -or ($_.name -like '*LSG*')} | Select Name -expandproperty name
foreach ($PrinterName in $Printers)
{
printui.exe /dl /n $PrinterName
}
}

How can I get psloggedon to pull from Active Directory?

get-content 'C:\assets.txt' | % {
$computer = $_
. 'c:\PSTools\PsLoggedon.exe' -accepteula -l -x \\$Computer 3>$null |
? {$_ -match '^\s{2,}((?<domain>\w+)\\(?<user>\S+))'} |
Select-Object `
#{n='Computer';e={$Computer}},
#{n='Domain';e={$matches.Domain}},
#{n='User';e={$Matches.User}} |
? user -notmatch '^Connecting$|^Users$|^NT$'
}
This is what I am using to get all of the currently logged on computers. Is there a way I can combine this with Get-ADUser so I ca pull straight from AD rather than from a txt document?
• Sorry, but currently there is no way through which you can integrate this ‘Psloggedon.exe’ utility with Active directory commands, i.e., ‘Get-AdUser’. But you can retrieve the details of currently logged on users on different computers in the network remotely by executing the below powershell function: -
‘ function Get-LoggedOnUser
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($comp in $ComputerName)
{
$output = #{ 'ComputerName' = $comp }
$output.UserName = (Get-WmiObject -Class win32_computersystem -ComputerName $comp).UserName
[PSCustomObject]$output
}
} ‘
The above script will give you currently logged on users on several computer systems in the network that you pass on in place of ‘COMPUTERNAME’ as below. Please note that you must give a list of computers separated by commas when using the above script for multiple computer systems.
If you have AD in your environment, then you can check the Domain Controller logs to see when an Active Directory user account logs on and it will also tell the machine that the user is logged onto. Refer the below links for more on this: -
http://technet.microsoft.com/en-us/library/cc787176(v=ws.10).aspx
http://technet.microsoft.com/en-us/library/bb742435.aspx
http://www.windowsecurity.com/articles/deciphering-authentication-events-domain-controllers.html
Also, find the below link for more information and reference on the above: -
https://4sysops.com/archives/how-to-find-a-logged-in-user-remotely-using-powershell/
Powershell script to see currently logged in users (domain and machine) + status (active, idle, away)
You would use PowerShell's Get-ADComputer to do this job, not Get-ADUser. Here's a script which does all this work for you. The below mainly lifted from the public domain here and only slightly modified. It pulls and pipes all AD domain computers into C:\Computers.txt, then PS-remotes into each computer in that list to find the logged in, interactive user, and their last login date. Gives you a report file named C:\LoggedOnResults.txt in a nice tabled format.
# Finds and pipes all AD domain computers into Computers.txt, then PS-remotes into each computer in the list to find the logged in, interactive user, and their last login date. Generates a report file named C:\LoggedOnResults.txt, in a nice tabled format.
# Deletes the current file C:\Computers.txt (if it exists)
$FileName = "C:\Computers.txt"
if (Test-Path $FileName) {
Remove-Item $FileName
write-host "$FileName has been deleted"
}
else {
Write-host "$FileName doesn't exist"
}
# 0. Capture all AD computers into a text file named Computers.txt
# importing dependancy, assuming it's already installed.
# Install RSAT for Windows workstation, AD DS role for Windows Server if missing
Import-Module "ActiveDirectory"
Get-ADComputer -Filter {(OperatingSystem -like "*windows*") -and (Enabled -eq "True")} | Select -Expand Name | Out-File "C:\Computers.txt"
# 1. Create scriptblock to target computer will execute
$SB = {
$explorerprocesses = #(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
if ($explorerprocesses.Count -eq 0) {
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME;
Username = [string]::Empty
LoggedOnSince = [string]::Empty
}
} else {
foreach ($i in $explorerprocesses) {
$Username = $i.GetOwner().User
$Domain = $i.GetOwner().Domain
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME ;
Username = '{0}\{1}' -f $Domain,$Username ;
LoggedOnSince = ($i.ConvertToDateTime($i.CreationDate)) ;
}
}
}
} # endof scriptblock
# 2. Create an empty array to store results
$results = #()
# 3. Query target computers using PSRemoting
Get-content "C:\Computers.txt" | ForEach-Object -Process {
$computer = $_
try {
$results += Invoke-Command -ComputerName $Computer -ScriptBlock $SB -ErrorAction Stop
} catch {
Write-Warning -Message "Faild to use PSremoting on $Computer because $($_.Exception.Message)"
}
}
# 4. Display the results
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize
# 5. Send results to a text file
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize | Out-File -FilePath "C:\LoggedOnResults.txt"

SMBShare Persmission on multiple Servers

i'm trying to create a script that checks for permissions of SmbShares on a few Servers.
The goal is to check if any share has a "everyone" permission. However i trying a find a more elegant solution than this mess:
$ADComputer = Get-ADComputer -Filter 'Name -like "Test-Server-*' | Where-Object { $excludeList -notcontains $_.name }
foreach ($server in $ADComputer) {
$shares = Invoke-Command -ComputerName $server -ScriptBlock { Get-SmbShare }
foreach ($share in $shares) {
$permissions = Invoke-Command -ComputerName $server -ScriptBlock { Get-SmbShareAccess -Name $share.name }
foreach ($permission in $permissions) {
if ( $permission ) ...
}
}
}
I can't wrapp my head around this. How can i solve this without using so many nested for loops?
Any ideas or hints?
A good pattern to follow here is:
Solve the whole problem for local execution on a single machine
Use Invoke-Command to repeat across multiple remote hosts
So let's start by writing a single function/script to solve the problem locally first:
function Find-SmbShareEveryoneGrant {
# We can pipe the output from one cmdlet to the next, and solve the problem in a single pipeline
Get-SmbShare |Get-SmbShareAccess |Where-Object { $_.AccessControlType -eq 'Allow' -and $_.AccountName -eq 'Everyone'}
}
Now that you have a function that can be repeated anywhere, it's as simple as calling Invoke-Command against all the servers - the -ComputerName parameter accepts an array, so explicitly looping over $ADComputers is not necessary:
Invoke-Command -ComputerName $ADComputer -ScriptBlock ${function:Find-SmbShareEveryoneGrant} |Select PSComputerName,Name,ScopeName,AccessRight
4-5 lines of code total, much more concise than all those nested loops and successive calls to Invoke-Command

Powershell out-file and name the file

I want to automate our auditing process, where we provide the local admin members, currently this is done with screenshots. I cobbled together the below code, which prompts for a server name and create a file with the local admin members. However this requires me to rename the file.
I want to be able to input the server name and have that also be the out-file name. I'm just not seeing the tree through the forest an how I go about it. Lots of stuff for appending file names but I didn't see anything for renaming a file you create.
Thanks
function get-localadmins {
[cmdletbinding()]
Param(
[string]$computerName
)
$group = get-wmiobject win32_group -ComputerName $computerName -Filter "LocalAccount=True AND SID='S-1-5-32-544'"
$query = "GroupComponent = `"Win32_Group.Domain='$($group.domain)'`,Name='$($group.name)'`""
$list = Get-WmiObject win32_groupuser -ComputerName $computerName -Filter $query
$list | % {$_.PartComponent} | % {$_.substring($_.lastindexof("Domain=") + 7).replace("`",Name=`"", "\")}
}
$Workstation = Read-Host "Computer Name"
get-localadmins $Workstation | Out-File c:\temp\ENTERSERVERNAME_LocalAdmin.txt
Try this out
| Out-File -FilePath "C:\Temp\${Workstation}_LocalAdmin.txt" -Append

Listing local administrator group membership on all domain computers

Complete powershell and scripting noob here - I don't even know enough to be dangerous. I need to query all PCs in the domain to determine what their local Administrators group membership is and send that output to a text/csv file.
I've tried numerous things like:
Import-Module -Name ActiveDirectory
Get-ADComputer -filter * |
Foreach-Object {
invoke-command {net localgroup administrators} -EA silentlyContinue |
} |
Out-File c:\users\ftcadmin\test.txt
Which gets me an identical list repeated but seems to be hitting every domain PC. I'm guessing it's not actually running on the remote PCs though. Also tried:
$computers = Get-Content -Path c:\users\ftcadmin\computers.txt
invoke-command {net localgroup administrators} -cn $computers -EA silentlyContinue
Get-Process | Out-File c:\users\ftcadmin\test.txt
which is limited by predetermined list of PCs in the computers.txt file. A third thing I tried was this:
$a = Get-Content "C:\users\ftcadmin\computers.txt"
Start-Transcript -path C:\users\ftcadmin\output.txt -append
foreach ($i in $a)
{ $i + "`n" + "===="; net localgroup "Administrators"}
Stop-Transcript
which seems to have the most potential except the output is just
computername1
====
computername2
====
etc without any listing of the group members.
Any ideas from the community?
Copy and paste this function into your PowerShell console.
function Get-LocalGroupMember
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]$ComputerName = 'localhost',
[Parameter()]
[ValidateNotNullOrEmpty()]
[pscredential]$Credential
)
process
{
try
{
$params = #{
'ComputerName' = $ComputerName
}
if ($PSBoundParameters.ContainsKey('Credential'))
{
$params.Credential = $Credential
}
$sb = {
$group = [ADSI]"WinNT://./$using:Name"
#($group.Invoke("Members")) | foreach {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
}
Invoke-Command #params -ScriptBlock $sb
}
catch
{
Write-Error $_.Exception.Message
}
}
}
Then, try this to use it:
Get-ADComputer -filter * | foreach {Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name }
If you'd like to do some formatting you could get a list of computers and have all of the members beside each one.
Get-ADComputer -filter * | foreach {
$members = Get-LocalGroupMember -Name 'Administrators' -ComputerName $_.Name
[pscustomobject]#{
'ComputerName' = $_.Name
'Members' = $members
}
}
This requires at least PowerShell v3. If you don't have that, I highly recommend upgrading to PowerShell v4 anyway.
In powershell version 5.1, the version that comes with WMF 5.1 or Windows 10 version 1607, there are now (finally) cmdlets for managing local users and groups. https://technet.microsoft.com/en-us/library/mt651681.aspx
For example, to get the members of the local Administrators group, you could use
Get-LocalGroupMember -Group "Administrators"
Unfortunately, these new cmdlets don't have a ComputerName parameter for running the command remotely. You'll need to use something like Invoke-Command to run the command remotely and the remote computer will need to have powershell version 5.1 as well.
The ComputerName parameter of Invoke-Command cmdlet doesn't accept pipeline input and it only accepts strings, so we first need to expand the name property returned by Get-ADComputer and store the strings in a variable. Thankfully the parameter accepts multiple strings, so we can simply use the $names variable in a single invoke-command call. Example below:
$names = Get-ADComputer -Filter * | Select-Object -ExpandProperty name
Invoke-Command -ScriptBlock {Get-LocalGroupMember -Group "Administrators"} -ComputerName $names
This is actually something that I have recently worked on a fair bit. It seems that there are two conventional ways to get members from the local Administrators group: WMI and ADSI.
In my opinion better method is to use a WMI query to get the members as this includes domain, so you know if the user/group listed is local to the server or is a domain account.
The simpler way using ADSI does not include this information, but is less likely to get Access Denied types of errors.
Towards this end I have cobbled together a script that checks AD for machines that have been active in the last 30 days (if it's not online, there's no need to waste time on it). Then for each of those it tries to do a WMI query to get the admin members, and if that fails it resorts to an ADSI query. The data is stored as a hashtable since that's a simple way to manage the nested arrays in my opinion.
$TMinus30 = [datetime]::Now.AddDays(-30).ToFileTime()
$Domains = 'ChinchillaFarm.COM','TacoTruck.Net'
$ServerHT = #{}
$Servers = ForEach($Domain in $Domains){Get-ADObject -LDAPFilter "(&(objectCategory=computer)(name=*))" -Server $Domain | ?{$_.lastLogonTimestamp -gt $TMinus30}}
$Servers.DNSHostName | %{$ServerHT.Add($_,$Null)}
ForEach($Server in ($Servers | ?{$(Test-Connection $_.DNSHostName -Count 1 -Quiet)})){
Try{
$GMembers = Get-WmiObject -ComputerName $Server -Query "SELECT * FROM win32_GroupUser WHERE GroupComponent = ""Win32_Group.Domain='$Server',Name='Administrators'"""
$Members = $GMembers | ?{$_.PartComponent -match 'Domain="(.+?)",Name="(.+?)"'}|%{[PSCustomObject]#{'Domain'=$Matches[1];'Account'=$Matches[2]}}
}
Catch{
$group = [ADSI]("WinNT://$Server/Administrators,group")
$GMembers = $group.psbase.invoke("Members")
$Members = $GMembers | ForEach-Object {[PSCustomObject]#{'Domain'='';'Account'=$_.GetType().InvokeMember("Name",'GetProperty', $null, $_, $null)}}
}
$ServerHT.$Server = $Members
}
Then you just have to output to a file if desired. Here's what I would do that should output something like what you want:
$ServerHT.keys|%{"`n"+("="*$_.length);$_;("="*$_.length)+"`n";$ServerHT.$_|%{"{0}{1}" -f $(If($_.Domain){$_.Domain+"\"}), $_.Account}}
This would give you something like the following if the first two servers responded to WMI queries and the third did not:
===========
ServerSQL01
===========
ServerSQL01\SQLAdmin
TacoTruck\TMTech
TacoTruck\Stan2112
======
XWeb03
======
ChinchillaFarm\Stan2112
============
BrokenOld486
============
TMTech
Guest
That last one would trigger some red flags in my book if somebody put the Guest account in the local admin group, but I suppose that's probably one of the reason that you're doing this in the first place.