User input to perform action - powershell

I'm building a script that I want to allow for user input to take action.
Currently this tool can checked all remote servers for any automatic stopped services. I do not want the script to automatically start them though. What I would like user input to start these services.
Write-Host "Checking for Stopped Services..."
$stoppedServices = Invoke-Command $Servers {
Get-CimInstance Win32_Service -ComputerName $Servers -Filter "startmode = 'auto' AND state != 'running'" |
Sort state | select name, state
} -Credential $Credential
if ($stoppedServices -ne $null) {
Clear-host
$stoppedServices
} else {
Clear-Host
Write-Host ("No stopped services found!")
}
So users have the ability to see the stopped services. I want to do 2 things from this. Write out the stopped services with the option, do you want to start these stopped services?
With that option then either exit or start the service. I'd imagine I can achieve this with another foreach loop but can never get it work.

You may want to use Get-WmiObject instead of Get-CimInstance, collect the matching services in a hashtable, and pass the list into Out-GridView with the -PassThru option. The gridview will return just the objects selected by the user which you can use to call the StartService() method on the service object in the hashtable.
$svc = #{}
Get-WmiObject Win32_Service -Computer $Servers -Credential $Credential -Filter "startmode = 'auto' AND state != 'running'" |
ForEach-Object { $svc[$_.DisplayName] = $_ }
$svc.Values | Select-Object SystemName, DisplayName, State |
Sort-Object State, SystemName, DisplayName |
Out-GridView -PassThru |
ForEach-Object { $svc[$_.DisplayName].StartService() }

Related

Delete a list of User Profiles

(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

Task scheduler running power shell script - Username error?

So I have a powershell script that when a user logs into a PC it emails details of the login to me, for example username,IP,location etc. It runs from task scheduler and has a trigger on login.
The script runs fine through powershell my issue I am having that no matter who logs in it always runs the script on my account in task scheduler hence always inputting my name as the Username even tough a different user may be logged in. I've tried calling the username multiple ways but always shows my name as It runs off my account.
For calling the username I have used :
$username = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$env:UserName
$env:UserDomain
All return the user of who is running the scheduled task.
Any help to get it to print the true user who has logged in would be much appreciated.
Thanks.
It may be because your scheduled task is executing with your credentials. There's no way around this, sadly. That said you can kind of side-step the behaviour by getting the user account associated with the explorer.exe process:
$username = Get-WmiObject Win32_Process -Filter "Name='explorer.exe'" | ForEach-Object { $_.GetOwner() } | Select-Object -Unique -Expand User
This should work fine, as long as you aren't ever in a situation where multiple users could be logged into a machine at the same time (fast user switching, terminal services etc.)
EDIT: To get what you're after, you might need something more like this
$op = #()
$owners = Get-WmiObject Win32_Process -Filter "Name='explorer.exe'" | ForEach-Object { $_.GetOwner() } | Select-Object -Unique -Expand User
foreach($owner in $owners)
{
$wmiprocs = get-wmiobject win32_process -filter "Name='explorer.exe'" | where { $_.getowner().user -eq $owner } | select-object processid
$procs = $wmiprocs | % { get-process -id $_.processid | select-object starttime }
$obj = new-object object
$obj | add-member -type noteproperty -name Username $owner
$obj | add-member -type noteproperty -name Logon ($procs | sort -descending)[0].starttime
$op += $obj
}
$op | ft -auto
Note that your PowerShell instance will need to be running as Admin to query the processes of other users.

How to check if an application is installed on multiple server - powershell

$comps = get-content C:\xyz\test.txt
foreach($comp in $comps)
{
$result = Get-WmiObject -Class Win32_Product -Computer $comp | sort-object Name | select Name | where { $_.Name -match “abc”}
$result | out-file -Append out.txt
}
There is a application abc , im looking to know whether this is installed in multiple servers.
Check this "Hey Scripting Guy" link. This link describes how to query for installed applications via the registry.
From the link:
Win32_Product: The Good, the Bad, and the Ugly
[Good] The Win32_Product WMI class represents products as they are installed >by Windows Installer.
If you choose to query Win32_Product class by using Get-WmiObject, you’ll find >yourself [Bad] waiting for your query (or application) to return [Ugly] a >consistency check of packages that are installed as it attempts to verify and >repair installs. (For more information, see Event log message indicates that >the Windows Installer reconfigured all installed applications).
...
Based on above link you can query your installed products via:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize
Additionally you can use Test-Path to check if a requested registry entry exists. Example:
if (test-path HKLM:\Software\abc) { write-host "Found" } else { Write-Host "Not found" }
To check for installed software on multiple servers:
$servers = Get-Content C:\xyz\servers.txt
$results = #()
foreach ($server in $servers) {
$session = New-PSSession -ComputerName $server -Credential (Get-Credential)
$results += Invoke-Command -Session $session -ScriptBlock {
test-path HKLM:\Software\abc
}
}
$results.GetType() # Dump type
$results | gm # Dump properties
$results | Format-Table
Hope that helps.

Getting services on remote servers through powershell

I've been trying to develop a powershell script to query a particular OU in AD and then get the services on each of those services including name, status, startmode, startname, description. I've gotten really close but not getting the results I expect. Can someone help?
The problem is if I run the command for one server specifically, I get the results I want. If I run for all servers in an OU it does not find any unique startnames (only the local accounts). I want a single CSV with the server name first
Script for single computer (getting results we want):
$ServerName = “server01”
Get-WmiObject win32_service -ComputerName $servername | Select #{N="ServerName";E={$ServerName}}, Name, Status, StartMode, StartName, description | Export-Csv "server_Services.csv" -NoTypeInformation
Script for querying OU (only returning services with local accounts):
$ServerOU = 'OU=Servers,DC=some,DC=domain'
$ServerList = Get-QADComputer -SearchRoot $ServerOU -SearchScope Subtree
$ServerList | ForEach {
$ServerNameTemp = $_.Name
Get-WMIObject Win32_Service |
Select #{N="ServerName";E={$ServerNameTemp}}, StartName, Name, StartMode, Status, description
} | Export-Csv -NoTypeInformation $ExportFile
This should do the trick i think but th following would wanna make a file for each server in the OU, it would keep overwriting the same file.
I also Changed Get-QADComputer into Get-ADComputer no specific need for quest powershell for this one :)
$ServerOU = 'OU=Servers,DC=some,DC=domain'
$ServerList = Get-ADComputer -SearchBase $ServerOU -Filter *
ForEach ($Server in $Serverlist)
{
Invoke-Command -ComputerName $Server.Name -ScriptBlock {
Get-WMIObject Win32_Service |
Select #{N="ServerName";E={[System.Net.Dns]::GetHostName()}}, StartName, Name, StartMode, Status, description}
} | Export-Csv -NoTypeInformation $ExportFile
This way it would send ti all to a variable first and then to a file.
$ServerOU = 'OU=Servers,DC=some,DC=domain'
$Services = #()
$ServerList = Get-ADComputer -SearchBase $ServerOU -Filter *
ForEach ($Server in $Serverlist)
{
$Services += Invoke-Command -ComputerName $Server.Name -ScriptBlock {
Get-WMIObject Win32_Service |
Select-Object #{N="ServerName";E={[System.Net.Dns]::GetHostName()}}, StartName, Name, StartMode, Status, description }
}
$Services | Export-Csv -NoTypeInformation $Exportfile
PS Remoting is automatically enabled from win 2012. If you are using any Win servers prior to that, you need to enable to WinRM services, to enable powershell remoting. This is very important, if you are trying to use "Invoke-Command" or any of those "*-Session" cmdlets against those servers.

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.