Deactivate running/ready Tasks and activate them again - powershell

I need a Powershell script, that deactivates a bunch of running/ready Tasks on multiple machines, so that we can update them. After we updated the machines i should be able to trigger the next part of the script, that activates specifically the tasks that the first part of the script deactivated.
Automatation and the Update part should not be part of the script, no timed actions needed or anything fancy.
I trigger Part 1 -> Script deactivates Tasks
I trigger Part 2 -> Script activates Tasks the Part 1 deactivated
For Part 1 i got this so far:
Invoke-Command -ComputerName xxx-11, xxx-12, xxx-14 -ScriptBlock {Get-ScheduledTask -TaskPath \yyy\ | Where {($_.State -like "running") -or ($_.State -like "ready")} | Disable-ScheduledTask }
I am unable to create a logic, that remembers what tasks were deactivated, so that the Part 2 can activate them again instead of simply activating EVERY task.

I guess what you want is to collect an array of objects storing the computername, the taskpath and the task name for each of the disabled tasks.
Try:
$result = Invoke-Command -ComputerName 'xxx-11', 'xxx-12', 'xxx-14' -ScriptBlock {
Get-ScheduledTask -TaskPath '\yyy\' | Where-Object {'running','ready' -contains $_.State} | ForEach-Object {
$null = $_ | Disable-ScheduledTask
# output an object to be collected in $result
$_ | Select-Object #{Name = 'ComputerName'; Expression = {$env:COMPUTERNAME}}, TaskPath, TaskName
}
}
# remove the extra properties Invoke-Command added
$result = $result | Select-Object * -ExcludeProperty PS*, RunSpaceId
You now have the info you need to reactivate the tasks later in variable $result
If you like you can save this to CSV file for later reference:
$result | Export-Csv -Path 'X:\Somewhere\DisabledTasks.csv' -NoTypeInformation

Related

Contain current variable name in Exported CSV

I am trying to write a simple script to scan through a list of workstations, pull their local user info, and export it to a CSV. The code works for doing that, but I am having trouble including any identifier of what workstation the information is related to. At the moment I just get a large list of users and their info.
$computerList = gc "C:\Temp\ComputerList.txt"
ForEach ($Computer in $computerList){
Get-LocalUser | Export-Csv C:\temp\passUser.csv -NoTypeInformation -Append }
You need to add the property Computer to the objects that are output from Get-LocalUser.
Also, you are currently running Get-LocalUser on your own local machine in each iteration.
Try
$computerList = Get-Content "C:\Temp\ComputerList.txt"
Invoke-Command -ComputerName $computerList -ScriptBlock {
Get-LocalUser |
Select-Object #{Name = 'Computer'; Expression = {$env:ComputerName}}, *
} | Export-Csv -Path C:\temp\passUser.csv -NoTypeInformation
Invoke-Command can take an array of computernames.
It also has a -Credential parameter with which you can specify the credentials of a user that has permissions to perform the actions inside the scriptblock

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.

-PipelineVariable isnt working as intended

I have this script that changes services per a csv file input
Import-CSV .\SSAS_services.csv |
ForEach-Object{
Get-Service $_.Service -ComputerName $_.Server -PipelineVariable svc|
Set-Service -Status $_.Task -StartupType $_.'Startup Type' -PassThru
} |
Select-Object MachineName, Name, Status, StartType, #{n='OldStatus';e={$svc.Status}}, #{n='OldStartType';e={$svc.StartType}} |
tee-object -FilePath '.\ChangeServices_LOG.txt' #-Append
Server,Service,Startup Type,Task
DCVPIM108,SQL Server Analysis Services (MSSQLSERVER),automatic,start
server2,"SQL Server Analysis Services (MSSQLSERVER), SQL Server Analysis Services (MSSQLSERVER) CEIP",Manual,stop
it works great, except for my -PipelineVariable svcis not working as intended. if a service was "stopped" and "Manual" before being changed to "running" and "automatic", it doesnt get the old values "stopped" and "Manual" for OldStatus and OldStartType
MachineName : DCVPIM108
Name : MSSQLServerOLAPService
Status : Running
StartType : Automatic
OldStatus : Running
OldStartType : Automatic
why is that?
The -PipelineVariable / -pv common parameter only works:
within a single pipeline.
in script blocks in later segments of the same pipeline.
Since you're using it in a pipeline that is nested inside the ForEach-Object script block, the commands in the outer pipeline cannot use it.
However, I suggest restructuring your command so that you don't need a pipeline variable for Get-Service anymore.
Instead,
-PipelineVariable $csvRow is used with Import-Csv, so that you can more easily refer to it even in nested pipelines (the alternative would be to define the variable explicitly at the start of the ForEach-Object script block as $csvRow = $_).
$svc is then declared as an -OutVariable, so that the original service state is captured before Set-Service is called to change it.
Getting a service, setting its startup type, and enriching the CSV-row object with additional information now all happen inside the ForEach-Object script block.
Import-CSV .\SSAS_services.csv -PipelineVariable csvRow | ForEach-Object {
Get-Service -Name $csvRow.Service -ComputerName $csvRow.Server -OutVariable svc |
Set-Service -Status $csvRow.Task -StartupType $csvRow.'Startup Type'
$csvRow | Select-Object MachineName, Name, Status, StartType,
#{n='OldStatus';e={$svc.Status}},
#{n='OldStartType';e={$svc.StartType}}
} | Tee-object -FilePath '.\ChangeServices_LOG.txt'
I guess what you want is to pass same object down the multiple pipes. I haven't use -PipeLineVariable much, but looks like it just creating a nicer alias for $_ . If you need to push something specific down the pipeline I guess you need to use write-ouput with custom object or hashtable. Below is a dummy sample, pushing down and modifying a hastable:
$services = "xagt" , "xbgm" , "XblGameSave"
$list = new-object System.Collections.ArrayList
$serv | foreach {
$svc = Get-Service $_ ; Write-Output #{Name = $svc.Name; Stat=$svc.Status}
} | foreach {$_.SomeNewItem = "new stuff"; $list.Add($_)}
But in your case one pipeline might be sufficient. Try something like that:
Import-CSV .\SSAS_services.csv | foreach {
$old = Get-Service $_.Service;
Set-Service -Name $_.Service -Status Running
$new = Get-Service $_.Service;
$data = $_.MachineName, $_.Service, $old.Status, $new.Status -join ","
Write-Host $data
$data >> Log.txt
}

Processing data returned from Invoke-Command as it's received?

I have a PowerShell script that I want to run on 5,000+ endpoints. The script enumerates a bunch of information about each host which I then want to be able to ingest into another program for analysis.
The problem I'm having is that Invoke-Command does not allow me to process the data returned from each machine as it's received. This is an issue because with 5,000+ endpoints I run into memory constraints on the machine I'm running Invoke-Command from.
Ideally I'd like to be able to execute a script block each time I receive a response from a machine. Something similar to this, for example:
$ProcessOutput = {
# $_ = Data returned from one machine
$_ | Out-File ($_.PSComputerName)
}
Invoke-Command -FilePath $ScriptPath -ComputerName $Computers -ProcessingScriptBlock $ProcessOutput
Is this already possible and I am overlooking something? Or is the best solution to just split my computer list into chunks and scan each chunk one by one (resulting in longer run times)?
You can use jobs, foreach -parallel (in a workflow) or runspaces (faster, but more complicated than jobs) to run parallel workloads. Here's an example using jobs which is the easiest to read.
$ProcessOutput = {
# $_ = Data returned from one machine
$_ | Out-File "$($_.PSComputerName).txt"
}
$Computers = "localhost", "frode-pc"
#Start job
$mainjob = Invoke-Command -FilePath $ScriptPath -ComputerName $Computers -AsJob
#Process results as they complete
while ($mainjob.State -eq "Running") {
$mainjob.ChildJobs | Where-Object { $_.State -eq 'Completed' } | Receive-Job | ForEach-Object $ProcessOutput
}
#Process last job(s)
$mainjob.ChildJobs | Where-Object { $_.State -eq 'Completed' } | Receive-Job | ForEach-Object $ProcessOutput
If performance is critical, use runspaces. Check out Invoke-Parallel for an easy-to-use implementation of runspaces

User input to perform action

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() }