I'm playing around with some Powershell commands and implementing them into scripts.
I noticed something that I cannot find much information on despite looking for a few hours so maybe you can help me here. This could also be lack of me searching for the wrong things, apologies ahead if that is the case.
What I'm experimenting with here is manipulating services through PS. Namely, for now, just getting the TaskName. Here is what I am doing:
PS C:\WINDOWS\system32> Get-ScheduledTask -TaskName 'Adobe*' | Select -ExpandProperty TaskName
Output:
Adobe Acrobat Update Task
Adobe Flash Player NPAPI Notifier
Adobe Uninstaller
This is all well and good. However, if I assign that command to a variable from within a powershell script and run the script:
$TaskNames = Get-ScheduledTask -TaskName 'Adobe*' | Select -ExpandProperty TaskName
Output:
Adobe Acrobat Update Task Adobe Flash Player NPAPI Notifier Adobe Uninstaller
So my questions here are:
Why does the formatting change when assigning the command to a variable and calling that variable as opposed to just writing the command explicitly
How can I get the variable call to format output as if I'm just typing the command in the first example
$TaskNames -join "`n"
... it's doing this probably because one is output of an object, and one is output of an array. If you do $TaskNames.getType() it should tell you it's an array. -join displays an array joined by whatever character you specify.
Related
I know well, this issue has already been discussed a lot. But even though I am unable to solve it myself, since I only use powershell scripting rarely...
I have the following commands, that I need to execute as a ".bat" file. If I just execute it within a command window, all works fine. But if I execute it within a ".bat" file, I get the error from the title...
powershell -Command "&{$devices = gwmi Win32_USBControllerDevice |%{[wmi] ($_.Dependent)}|select DeviceID;$devices.DeviceID | %{Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Enum\$_"}| select FriendlyName,HardwareID}"
Within the ".bat" file I added an additional line with "pause", that seems not to be the cause of the problem...
I would be very happy if someone more advanced in scripting could tell me what 1 to 10 chars need to be added/changed in the command to make it work ;-)
Big thanks in advance!!!
Regards,
Erik
Based upon your response to my initial comment, the reason for that error is that you're using the % alias for ForEach-Object, and in a batch file, the % character needs to be escaped using another % character.
Including the nested double-quote escaping with backward slashes, I've already advised, your resulting command would therefore be:
powershell -Command "&{$devices = gwmi Win32_USBControllerDevice |%%{[wmi] ($_.Dependent)}|select DeviceID;$devices.DeviceID | %%{Get-ItemProperty -Path \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\"}| select FriendlyName,HardwareID}"
Tested result example:
If I was doing this I'd probably have done it a little differently:
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command^
"((Get-CimInstance Win32_USBControllerDevice).Dependent).DeviceID |"^
"ForEach-Object {"^
"Get-ItemProperty -Path \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\""^
"} | Select-Object FriendlyName, HardwareID"
And using shortened commands/aliases:
PowerShell -NoP "((gcim Win32_USBControllerDevice).Dependent).DeviceID|%%{gp \"HKLM:\SYSTEM\CurrentControlSet\Enum\$_\"}|Select FriendlyName,HardwareID"
I've inherited a server that runs Windows scheduled tasks for several hundred various processes (mostly it kicks off custom PowerShell scripts) - the schedules for these tasks can be as frequently as every 15 minutes, or as infrequently as once per year on a specific date.
How can I query task scheduler using PowerShell (or really any other means also) to determine what if any tasks will be running between a specific date-time range in the future? This is necessary so we can for example schedule and perform maintenance on this server, and others that it interacts with.
I should start out by mentioning this is a deceptively complex topic. There are few reasons for this, including but not limited to:
Scheduled Tasks as objects can be very complex with widely variable schedules, repetition intervals, other trigger types, and a plethora of other configuration points.
The non-GUI, meaning CLI, cmdlet, and/or API tools have changed over the years. And, it's not always easy to string them together to solve the complexities cited in point 1.
WARNING: This report is going to be very tough to write. Before you embark on a challenge like this you should look for any preexisting tools that may inventory or lend other transparency to the Task Scheduler.
Also, it may be better to define the maintenance window as a matter of policy. Then you can configure jobs to avoid the window. At a glance, this strikes me as much easier than writing a difficult program to assist in the cherry-picking of a time slot.
If you choose to move forward here are a few tools that may help:
Schtasks.exe: A surprisingly capable CLI tool that's been around a long time.
The TaskScheduler PowerShell module: A PowerShell module written around scheduling COM objects. It was originally part of the Windows 7 PowerShell pack, but these days you can get it through the PowerShell Gallery. The easiest way is through typical module cmdlets like Find-Module & Install-Module
The newer ScheduledTasks module installed by default on later Windows systems. It's Written around Cim (WMI) and is installed by default in later versions of PowerShell / Windows.
CAUTION: It's easy to confuse the latter 2 tools. Not only are the names are very similar, but there are overlap and similarity between the commands they contain. For example, both modules have a Get-Scheduledtask cmdlet. Two ways to deal with the confusion:
When importing a Module with the Import-Module cmdlet use the -Prefix parameter to add a prefix to the none part of the cmdlet. then use that prefix when calling the cmdlet thereafter.
Call cmdlets with a qualified name like TaskScheduler\GetScheduledTasks
Now to get at the scheduling data. In my experience, the needed details are only exposed through the task's XML definition. Again, you're going to bump up against the complexity that comes with a wide range of scheduling and/or trigger options. You can read about the XML schema here
Here are some examples of how to access the XML data:
schtasks.exe /query /s pyexadm1 /tn <TaskName> /XML
In this case, you'll have to do additional string manipulation to isolate the XML then cast it to [XML] so you can work with it in a typical PowerShell manner. Obviously, there will be challenges to leveraging this tool more broadly. However, it's very handy to know for quick checks and work, especially where the next tool is not immediately available.
Note: if you don't cite the /TN argument all tasks will be returned. While the next method is easier, it's good to know this approach, it will be handy while you are developing.
The next example uses the older TaskScheduler module (#2 above):
$TaskXML = [XML](TaskScheduler\Get-ScheduledTask -ComputerName <ComputerName>-Name <TaskName>).XML
Note: Above assumes no prefix was used. So, you must cite the source module to prevent confusion with the ScheduledTask module.
This example loads the XML text and converts it to an XmlDocument object in a single line. Then you can access data about the task like below:
$TaskXML.Task.Triggers.CalendarTrigger
This may yield output like:
StartBoundary Enabled ScheduleByWeek
------------- ------- --------------
2020-09-14T08:00:00 true ScheduleByWeek
You can run this in mass by leveraging the pipeline, which might look something like below:
$XMLTaskData =
TaskScheduler\Get-ScheduledTask -ComputerName <ComputerName> -Recurse |
ForEach-Object{ [XML]$_.XML }
In the above pipeline example the resulting $XMLTaskData is an array each element of which is a respective XML task definition.
Note: Use of the -Recurse switch parameter. Given the high number of tasks, I wouldn't be surprised if they were organized into subfolders.
Similarly, you can also use the Export-ScheduledTask cmdlet from the ScheduledTasks module:
$TaskXML = [XML](Export-ScheduledTask -CimSession <ComputerName> -TaskName <TaskName>)
And you can leverage the pipeline like this:
$XMLTaskData =
Get-ScheduledTask -CimSession <ComputerName> |
Export-ScheduledTask |
ForEach-Object{ [XML]$_ }
Like the other piped example, this results in an array of XML task definitions.
Note: In this case, there is no -Recurse parameter. You can specifically cite paths though.
With any of these approaches you obviously need some familiarity with working with XML objects in PowerShell, but there are tons of tutorials or other resources for that.
Again, the complexity here is in dealing with many trigger types and scheduling paradigms. On your road to getting a Minimally Viable Program (MVP), you may want to use these techniques to inventory the existing tasks. That can help you prioritize your development process.
A final point; knowing when a task is going to run may be quite different than know when it's running. For example, a task may run a 1:00 PM, but the duration of the job is variable and unaccounted for. That strikes me as very difficult to contend with. You may need another procedure to look for task completion events in the event logs. You may also need to consider execution time limits which can be found in the XML data.
Here is a script to get the next running jobs
Get-ScheduledTask |
Foreach-object { Get-ScheduledTaskInfo $_} |
where-object {!($_.NextRunTime -eq $null)} |
Select-object Taskname, NextRunTime |
Sort-object -property NextRunTime
I use the following command to run a pipeline.
.\Find-CalRatioSamples.ps1 data16 `
| ? {-Not (Test-GRIDDataset -JobName DiVertAnalysis -JobVersion 13 -JobSourceDatasetName $_ -Exists -Location UWTeV-linux)}
The first is a custom script of mine, and runs very fast (miliseconds). The second is a custom command, also written by me (see https://github.com/LHCAtlas/AtlasSSH/blob/master/PSAtlasDatasetCommands/TestGRIDDataset.cs). It is very slow.
Actually, it isn't so slow processing each line of input. The setup before the first line of input can be processed is very expensive. That done, however, it goes quite quickly. So all the expensive code gets executed once, and only the fairly fast code needs to be executed for each new pipeline input.
Unfortunately, when I want to do the ? { } construct above, it seems like PowerShell doesn't keep the pipe-line as it did before. It now calls me command a fresh time for each line of input, causing the command to redo all the setup for each line.
Is there something I can change in how I invoke the pipe-line? Or in how I've coded up my cmdlet to prevent this from happening? Or am I stuck because this is just the way Where-Object works?
It is working as designed. You're starting a new (nested) pipeline inside the scriptblock when you call your command.
If your function is doing the expensive code in its Begin block, then you need to directly pipe the first script into your function to get that advantage.
.\Find-CalRatioSamples.ps1 data16 |
Test-GRIDDataset -JobName DiVertAnalysis -JobVersion 13 -Exists -Location UWTeV-linux |
Where-Object { $_ }
But then it seems that you are not returning the objects you want (the original).
One way you might be able to change Test-GRIDDataset is to implement a -PassThru switch, though you aren't actually accepting the full objects from your original script, so I'm unable to tell if this is feasible; but the code you wrote seems to be retrieving... stuff(?) from somewhere based on the name. Perhaps that would be sufficient? When -PassThru is specified, send the objects through the pipeline if they exist (rather than just a boolean of whether or not they do).
Then your code would look like this:
.\Find-CalRatioSamples.ps1 data16 |
Test-GRIDDataset -JobName DiVertAnalysis -JobVersion 13 -Exists -Location UWTeV-linux -PassThru
I need to run some PowerShell scripts across various operating systems. Most of them are in English version, however, some are localized for example German, French, Spanish, etc. The problem is local system administrators mostly do not now PowerShell and in the case the script fails and throws an error at them, instead of reading it they just send screenshots of such error messages to me and if the cause to this error is not obvious I am stuck with typing it to g. translate to find out what is going on.
Is there a switch I can run the whole script or single command with or a parameter or any other way to force errors in PowerShell to be displayed in English instead of the language that is default for that particular machine?
You can change the pipeline thread's CurrrentUICulture like so:
[Threading.Thread]::CurrentThread.CurrentUICulture = 'fr-FR'; Get-Help Get-Process
I'm on an English system but before I executed the line above, I updated help like so:
Update-Help -UICulture fr-FR
With that, the Get-Help call above gave me French help on my English system. Note: if I put the call to Get-Help on a new line, it doesn't work. Confirmed that PowerShell resets the CurrentUICulture before the start of each pipeline which is why it works when the commands are in the same pipeline.
In your case, you would need to have folks install English help using:
Update-Help -UICulture en-US
And then execute your script like so:
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'; .\myscript.ps1
[Threading.Thread]::CurrentThread.CurrentUICulture only affects to current one-liner, so you can use it for execution of single .ps1 file.
If you want to change messages to English throughout every command in a PowerShell window, you have to change the culture setting cached in PowerShell runtime with reflection like this:
# example: Set-PowerShellUICulture -Name "en-US"
function Set-PowerShellUICulture {
param([Parameter(Mandatory=$true)]
[string]$Name)
process {
$culture = [System.Globalization.CultureInfo]::CreateSpecificCulture($Name)
$assembly = [System.Reflection.Assembly]::Load("System.Management.Automation")
$type = $assembly.GetType("Microsoft.PowerShell.NativeCultureResolver")
$field = $type.GetField("m_uiCulture", [Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Static)
$field.SetValue($null, $culture)
}
}
(from https://gist.github.com/sunnyone/7486486)
I am currently trying the Powershell web server PoSH (http://poshserver.net/) for some administration reports. But i don't know how to format ouput.
From the start: i start the console with the default shortcut, with admin rights. I type Import-Module PoSHServer, then Start-PoSHServer. The web server starts, then i create a simple index.ps1 file, with just one line of code in the body section: $(command).
For example, i want to use the Get-Service Mpssvc command, but what i obtain is :
System.ServiceProcess.ServiceController
I try Get-Service MpsSvc | Select Name,Status. Output:
#{Name=MpsSvc; Status=Running}
Same thing for cmdlets Get-Process, i have an output with list of processes but it appears like this: System.Diagnostics.Process (AcroRd32) ...
However, some cmlets just like the Get-Date (used in the Posh demonstration web page) works fine and have a "normal" output.
I read the documentation, but there is no example which can help me for that.
How can i write powershell code to obtain a "clean" and console-like output?
I just downloaded and installed Posh-Server yesterday after reading this post.
If you want output to look like console inside a web-page you are probably looking at this from the wrong angle, you need to think string not console. Your code is supposed to be running inside of a here-string, in the example. So I got the hint here that the standard console formatter does not apply, posh-server will use whatever it wants to to turn your returned object into a STRING!. Your code output will get turned into a string using whatever formatter it deems applies unless you explicitly return a string - which the example script does correctly do. So try this on the console
get-process "power*" | out-string -width 80
And then try it in your posh-server script.
You probably really wanted this:
Get-Service MpsSvc | Select Name,Status | out-string -width 120
Hope that helps - I think the lack of examples in this project is a good thing because this is really a very simplistic web-server; lots of conceptual thinking required before you even start :) .