Powershell problem with service status searching - windows

Hi guys its maybe a easy question for you but im newbie from powershell so can you pls help me?
In school I got an assignment where I needed to make a menu, a script that could search from service to status, and a script that could search from status to service, and it looks like this:
elseif ($menu -eq "2") {
$statusbank = (Get-Service).Status
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if ($statusbank.Contains([string]$sstatuss)) {
$Information = (Get-Service | Where-Object {$_status -eq $sstatuss}).Name | format-list -property Name
Write-Host $Information
}
}
i really dont understand where my problem is.
It dosn't work: It doesn't do anything and then just ends the script
If i debug, i only see it will skip this, even they are a lot of true value in $statusbank :
if ($statusbank.Contains([string]$sstatuss)) {

Try using this instead:
elseif ($menu -eq "2")
{
$statusbank = Get-Service
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if($sstatuss -match '^(Running|Stopped)$' -and $sstatuss -in $statusbank.Status)
{
$statusbank | Where-Object Status -EQ $sstatuss |
Format-Table -Property Name,Status
}
}

To complement Santiago Squarzon's helpful answer with an optimization:
# Prompt until a valid service status identifier is entered.
do {
try {
[System.ServiceProcess.ServiceControllerStatus] $sStatus =
Read-Host "Please specify the desired service status (e.g., Running or Stopped)"
break # A valid value was entered, exit the loop
} catch { }
Write-Warning "Unknown status; please specify one of: $([Enum]::GetNames([System.ServiceProcess.ServiceControllerStatus]))"
} while ($true)
# Now output the names of all services that are in the specified state, if any:
(Get-Service | Where-Object Status -eq $sStatus).Name
Casting the user input (which is always a string) to type [System.ServiceProcess.ServiceControllerStatus] (the type of the .Status property of the objects returned by Get-Service) is used to ensure that a valid service-status identifier was entered.
As for what you tried:
Leaving the inefficiency of calling Get-Service twice aside, your primary problem was the use of the .Contains() .NET array method (implemented via the IList interface):
.Contains() performs no on-demand type conversions, so looking for a string ($sstatuss) in your array of [System.ServiceProcess.ServiceControllerStatus] values ($statusbank) never succeeds.
By contrast, PowerShell's -contains operator does perform on-demand type conversions (as PowerShell generally does) and is notably also case-insensitive (as PowerShell generally is). The same applies to functionally equivalent, but operands-reversed -in operator.
To illustrate the difference:
# Sample array with [System.ServiceProcess.ServiceControllerStatus] elements.
$array = [System.ServiceProcess.ServiceControllerStatus]::Running,
[System.ServiceProcess.ServiceControllerStatus]::Stopped
# WRONG.
$array.Contains('running') # !! always $false with a [string] as input
# OK.
$array -contains 'running' # -> $true - on-demand type conversion
# from string to [System.ServiceProcess.ServiceControllerStatus]
In a nutshell: -contains is in effect using the -eq operator against each element behind the scenes, so the latter's automatic type conversions and case-insensitivity apply. See the bottom section of this answer for more information about -contains and -in.
Pitfall: Due to having the same name, there's potential for confusion with the .Contains() string method, which functions differently, however: it performs literal substring matching, and there is no direct operator equivalent in PowerShell for that - see this answer.
Also:
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer

Related

How to Stop powershell form Converting the variable assigment of a boolean to Uppercase letter

I am trying to format a json and do dynamic variable assignment but once the boolean value is assigned powershell is changing the casetype for the first letter to uppercase.
I still want to maintain the case type for my input values both lowercase and uppercase as it is in my json file.
Any help?
{
"input": true,
"variable": "Relative path",
}
$path= "L:\test\parameter.json"
$json = Get-Content $path | ConvertFrom-Json
foreach ($data in $json.PSObject.Properties) { New-Variable -name $($data.name) -value $($data.value) -Force}
echo $input
True ->>> I want it to be "true" and the value of variable to still be "Relative Path"
Generally, you mustn't use $input as a custom variable, because it is an automatic variable managed by PowerShell.
Leaving that aside, ConvertFrom-Json converts a true JSON value - a Boolean - into the equivalent .NET Boolean (System.Boolean, represented as [bool] in PowerShell). The representation of this value in PowerShell is $true.
Printing this value to the console (host) effectively calls its .ToString() method in order to obtain a string representation, and that string representation happens to start with an uppercase letter:
PS> $true
True
If you need an all-lowercase representation, call .ToString().ToLower(), or, for brevity, use an expandable string and call .ToLower() on it:
PS> "$true".ToLower() # In this case, the same as $true.ToString().ToLower()
true
If you want to apply the all-lowercase representation automatically to all Booleans, you have two options:
Modify the data, by replacing the Boolean values with their desired string representations:
This answer shows how to walk a [pscustomobject] object graph returned by ConvertFrom-Json and update its (leaf) properties.
Preferably, only modify the display formatting of [bool] values, without needing to modify the data, as zett42 suggests.
See below.
(Temporarily) overriding the .ToString() method of type [bool]:
Update-TypeData can be used to override the members of arbitrary .NET types, but there is a limitation due to a bug - reported in GitHub issue #14561 - present up to at least PowerShell 7.2.2:
A .ToString() override is not honored when you cast an instance to [string] (e.g., [string] $true) or when you use it in an expandable string (e.g, "$true")
However, with implicit stringification of Booleans, as happens during for-display formatting, it does work:
# Override the .ToString() method of [bool] (System.Boolean) instances:
# Save preexisting type data, if any.
$prevTypeData = Get-TypeData -TypeName System.Boolean
# Add a ScriptMethod member named 'ToString' that outputs an
# all-lowercase representation of the instance at hand. ('true' or 'false')
Update-TypeData -TypeName System.Boolean `
-MemberType ScriptMethod -MemberName ToString `
-Value { if ($this) { 'true' } else { 'false' } } `
-Force
# Output a sample custom object with two Boolean properties.
[pscustomobject] #{
TrueValue = $true
FalseValue = $false
}
# Restore the original behavior:
# Note: In production code, it's best to put this in the `finally`
# block of try / catch / finally statement.
# Remove the override again...
Remove-TypeData -TypeName System.Boolean
# ... and restore the previous data, if any.
if ($prevTypeData) { Update-TypeData -TypeData $prevTypeData }
Note: You cannot scope Update-TypeData calls, which invariably take effect session-globally, so it's best to remove the override again with Remove-TypeData and restore any preexisting type data, if any, as shown above.
zett42 has generalized the approach above to create a general-purpose Invoke-WithTemporaryTypeData function that scopes type-data modifications to a given piece of code (script block): see this Gist.
Output (note the all-lowercase property values):
TrueValue FalseValue
--------- ----------
true false

How does one Transform a Collection of Objects into a Collection of new Objects of another Type in Powershell?

Supposing I have a method that gets a collection of enums of my enum type ProjectName and a have a collection of Server objects I want to associate with those ProjectNames in a type called Project; how would I do this in a Powershell Select-Object (or some other equivalent of LINQ's Select).
The C# equivalent of what I want to produce in Powershell is this:
var servers = new[]
{
new Server(/*someOtherProjectsCollection goes here*/),
new Server(/*someOtherProjectsCollection goes here*/),
new Server(/*someOtherProjectsCollection goes here*/)
};
var projects = GetProjectNames().Select(projectName => new Project(projectName, servers.Where(server => server.Projects.Any(serverProject => serverProject.Name == projectName))));
But what I have is this:
$servers = [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/)
$projects = (GetProjectNames()) | Select-Object {
$selectedProjectName = $_
return [Project]::new($_, ($servers | Where-Object { $_.projects.Where({ $_ -eq $selectedProjectName }).Count -gt 0 }))
}
When I try and read $projects back in Powershell LSE (whilst on a breakpoint after this last line), it just returns the code as a string and I can't even cast it to [Project[]]. I think the problem might be with the use of curly braces with Select-Object but I'm not sure how else to create a new Project object within the Select-Object.
You want ForEach-Object instead of Select-Object to return a new [Project] instance for each project name; also, your code can be streamlined:
$projects = GetProjectNames | ForEach-Object {
$projectName = $_
[Project]::new(
$projectName,
$servers.Where({ $_.projects -eq $projectName })
)
}
Select-Object is for creating new custom objects based on select properties from the input objects; by contrast, you're constructing a new, specific type instance from each input object, which must be done in a ForEach-Object call, where you explicitly control the output.
$_.projects -eq $projectName as a conditional relies on PowerShell's ability to use -eq with an array as the LHS, in which case filtering is performed, and a filtered subarray is returned; since .Where() interprets the script block's output as a Boolean, an empty subarray is interpreted as $false, whereas one with at least one element is interpreted as $true.
Also note that you don't need an explicit return, given PowerShell's implicit output behavior: since the newly constructed [Project] instance isn't assigned to a variable or sent elsewhere, it is automatically returned.

FuncOne() -and FuncTwo() not working

This is my code:
if (FuncOne($valOne) -and FuncTwo($valTwo)) {
...
}
When both of those functions evaluate to $false, the code within the if statement still executes. Why is that? This also does not work as expected:
if (FuncOne($valOne) -eq $true -and FuncTwo($valTwo) -eq $true) {
...
}
When written this way, it works as expected.
$a = FuncOne($valOne)
$b = FuncTwo($valTwo)
if($a -and $b) {
...
}
What's going on?
This is a syntax issue.
The correct syntax for invoking functions in PowerShell is not:
functionName($arg1[,$arg2,$arg3])
but rather:
functionName [-parameterName1] $arg1 [[-parameterName2] $arg2]
Now, let's apply this to your if() sample expression:
FuncOne $valOne -and FuncTwo $valTwo
You'll notice that explicit parameter names (-ParameterName) and operators (-and) are completely indistinguishable - there's no way for the parser to tell whether -and is supposed to be interpreted as a parameter name or an operator. Additionally, the string literal FuncTwo can just as well be interpreted as a parameter argument, which is exactly what happens.
In order for the parser to know that you intend it to treat the expression as two separate expressions, force evaluation order with ():
if ((FuncOne $valOne) -and (FuncTwo $valTwo)) {
# They both returns a truthy value
}
You actualy need to place the function in parenthese in order to invoke it:
if ((FuncOne($valOne)) -and (FuncTwo($valTwo))) {
...
}

Powershell Auto-Complete on ValidationSet

Creating a Validation Set is easy.
param(
[Parameter(Mandatory=$true)]
[ValidateSet('Ding','Dong')]
[string]$bellState,
[Parameter(Mandatory=$true)]
[ValidateSet('Dead','Alive')]
[string]$witchesState
)
It provides free auto completion if your Powershell version is >2
However it's not so helpful if you don't pass in the params at the start.
cmdlet Untitled2.ps1 at command pipeline position 1
Supply values for the following parameters:
bellState: Dib
witchesState: Alive
C:\Users\cac\Untitled2.ps1 : Cannot validate argument on parameter 'bellState'. The argument "Dib" does not belong to the set "Ding,Dong" specified by the ValidateSet attribute. Supply an argument that is in the set and then
try the command again.
+ CategoryInfo : InvalidData: (:) [Untitled2.ps1], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Untitled2.ps1
This time there is no tab completion or clues :(
If you type in something invalid you do get a useful error:
"The argument "Dib" does not belong to the set "Ding,Dong""
However this error is thrown at the end of the params not at the time of the original mistake and there isn't an option to try again.
Has anyone found a way of extending this validation to be any more user friendly in the instance it initiated without passed in parameters.
While it might not be exactly what you wanted I would think a simple addition to the script would be to add HelpMessages to your parameters. That way the user has the option to get more information about what they are about to type.
param(
[Parameter(Mandatory=$true,
HelpMessage="You need to pick ding or dong silly")]
[ValidateSet('Ding','Dong')]
[string]$bellState,
[Parameter(Mandatory=$true)]
[ValidateSet('Dead','Alive')]
[string]$witchesState
)
So when called without specifying parameters...
Supply values for the following parameters:
(Type !? for Help.)
bellState: !?
You need to pick ding or dong silly
bellState:
If I remove the mandatory bit and add some code after parameter block I can get the result I want. I understand this might make a pretty useless Cmdlet for piping etc. so I see why it isn't default behavior.
I'm getting around this by having a function for doing the actual work with mandatory params and having a separate helper function to get user input.
It's not so long winded or tricky to get the following to do pretty much whatever you want.
There's an article here
if(!($bellState)){
$title = "Pick a Bell State"
$message = "You need to pick ding or dong silly"
$Ding = New-Object System.Management.Automation.Host.ChoiceDescription "Ding", `
"First Strike."
$Dong = New-Object System.Management.Automation.Host.ChoiceDescription "Dong", `
"Second Strike."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($Ding, $Dong)
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
switch ($result)
{
0 { $type = "Ding" ; "First Strike."}
1 { $type = "Dong" ; "Second Strike."}
}
}

Correct conditional logic for multiple flags

I have a file that resembles the following
C:\path\AP1\com1\web.config
C:\path\AP2\com1\web.config
C:\path\AP1\com2\web.config
C:\path\AP2\com2\web.config
C:\path\AP1\com4new\web.config
C:\path\AP2\com4new\web.config
C:\path\AP1\thirdFolder\web.config
C:\path\AP2\thirdFolder\web.config
This file gets output on every server regardless of environment. I am creating a validation script to only include the relevant lines for each server.
An example of what the server data looks like is this
<server compName="serverName102">
<ip>192.168.1.1</ip>
<type>App</type>
<env>test</env>
<instances>Com2, Com3</instances>
<AppPools>bo, fo</AppPools>
</server>
If a server's AppPools setting contains fo, then I need to include any lines that contain AP1. If AppPools contains bo, then I need AP2, and obviously if it contains both I need both lines.
If a server's instances contains Com2, then I need to include any lines that contain com2 OR com4new. Com3 will be the thirdFolder lines.
I currently have several if statements that look like the following
if ($serverInst -like "*com2*"){
$refinedResults += $allResults | where-object {$_ -like "*com2*" -or $_ -like "*com4new*"}
}
The problem is that I am almost positive that how I am doing this is not nearly the best way, or how I should be doing this. Can anybody provide some insight to the best way to perform this validation?
The major problem I see with your approach is the fact that you might end up with duplicates in your $refinedResults collection.
To work around this, you can employ a HashSet of strings.
If you try to add a value to a HashSet and it already exists, the operation simply returns false and nothing happens to the Set.
Assuming that $allResults is an array of strings, you could do this:
$refinedResults = New-Object System.Collections.Generic.HashSet[String]
if ($serverInst -like "*com2*")
{
$refinedResults.UnionWith([String[]]($allResults |? {$_ -like "*com2*" -or $_ -like "*com4new*"}))
}
For readability (and performance if you plan on processing more than one entry at a time), you might want to define your rules and extract from the $allResults before you start matching:
[String[]]$resultsCom2 = $allResults |? {$_ -like "*com2*" -or $_ -like "*com4new*"}
[String[]]$resultsCom3 = $allResults |? {$_ -like "*thirdfolder*"}
$refinedResults = New-Object System.Collections.Generic.HashSet[String]
if ($serverInst -like "*com2*")
{
$refinedResults.UnionWith($resultsCom2)
}
if ($serverInst -like "*com3*")
{
$refinedResults.UnionWith($resultsCom3)
}
If the instantiation of $refinedResults fails, add:
Add-Type -AssemblyName System.Core
to your script before it

Resources