I'm having a little trouble with a PS script right now.
What I am trying to do:
Depending on which OU a computer belongs to, it should get a different printer mapped.
I am trying this with a switch condition, but regardless of what I try the condition seems to be always TRUE (although I know it isn't)
When I type in the condition into PowerShell manually, I get the correct values if the condition is TRUE or FALSE. But as soon as I use it in the switch, the condition seems to be always TRUE.
What I have so far:
With dsquery I check if a computer belongs to a specific OU.
If a value is returned, which only happens if the query succeeds, I put it into my $SwitchDump variable (Condition TRUE).
From my understanding, if a device is not found in the OU, there is no value that will be passed to my $SwitchDump variable and hence should be $null right?
But it keeps mapping the printer.
Switch ($SwitchDump = dsquery computer $OU_TO_SEARCH_IN|findstr $env:COMPUTERNAME | Out-String)
{
$SwitchDump -ne $null {Add-Printer -ConnectionName \\$PrintServer\$DesiredPrinter}
}
Or am I just barking up the wrong tree?
Any ideas would be greatly appreciated.
$SwitchDump = dsquery computer $OU_TO_SEARCH_IN | findstr $env:COMPUTERNAME | Out-String
Switch ($SwitchDump)
{
{$_ -ne $null} {Add-Printer -ConnectionName \\$PrintServer\$DesiredPrinter}
}
You need to use $_ to represent the variable being tested by the Switch if you want to do anything beyond simple comparisons for values. You also need to make those comparisons a scriptblock by using { }.
You're assigning the dsquery to $SwitchDump... which will [almost] always return true ;-)
You probably want to perform an equality check i.e.
Switch ($SwitchDump -eq dsquery computer $OU...
Also it looks like you have your switch syntax slightly off: https://ss64.com/ps/switch.html
$SwitchDump = dsquery computer $OU_TO_SEARCH_IN|findstr $env:COMPUTERNAME
if (-not $SwitchDump) {
Write-Output "SwitchDump is empty"
}
switch ($SwitchDump) {
$null { Write-Host "SwitchDump = NULL" ; break; }
"value1" { Write-Host "SwitchDump = value1"; break; }
"value2" { Write-Host "SwitchDump = value2"; break; }
"value3" { Write-Host "SwitchDump = value3"; break; }
default { Write-Host "None of the above" ; break; }
}
Related
function Show-Menu { #Create the Show-Menu function
param ([string]$Title = 'Functions') #Sets title
Clear-Host
Write-Host "`t6: Reboot History." -foregroundcolor white
Write-Host "`tQ: Enter 'Q' to quit."
} #close of create show menu function
#Begin Main Menu
do
{
Show-Menu #Displays created menu above
$Selection = $(Write-Host "`tMake your selection: " -foregroundcolor Red -nonewline; Read-Host)
switch ($selection) #Begin switch selection
{
#===Reboot History===
'6' {
$Workstation = $(Write-Host "Workstation\IP Address" -nonewline -foregroundcolor DarkGreen) + $(Write-Host "(Use IP for remote users)?: " -NoNewline; Read-Host)
$DaysFromToday = Read-Host "How many days would you like to go back?"
$MaxEvents = Read-Host "How many events would you like to view?"
$EventList = Get-WinEvent -ComputerName $Workstation -FilterHashtable #{
Logname = 'system'
Id = '41', '1074', '1076', '6005', '6006', '6008', '6009', '6013'
StartTime = (Get-Date).AddDays(-$DaysFromToday)
} -MaxEvents $MaxEvents -ErrorAction Stop
foreach ($Event in $EventList) {
if ($Event.Id -eq 1074) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Restart'
UserName = $Event.Properties.value[6]
}
}
if ($Event.Id -eq 41) {
[PSCustomObject]#{
TimeStamp = $Event.TimeCreated
Event = $Event.Id
ShutdownType = 'Unexpected'
UserName = ' '
}
}
}
pause
}
}
}
until ($selection -eq 'q') #End of main menu
Works perfectly fine if I remove the script from the switch and run it separately, but as soon as I call it from the switch it still asks for the workstation/IP, how many days, and max events, but just outputs nothing.
Here is what it looks like when it works:
How many days would you like to go back?: 90
How many events would you like to view?: 999
TimeStamp Event ShutdownType UserName
--------- ----- ------------ --------
12/23/2022 12:20:55 AM 1074 Restart Username
12/20/2022 1:00:01 AM 1074 Restart Username
12/17/2022 12:21:54 AM 1074 Restart Username
12/13/2022 8:57:40 AM 1074 Restart Username
This is what I get when I run it within the switch menu
Workstation\IP Address(Use IP for remote users)?: IP Address
How many days would you like to go back?: 90
How many events would you like to view?: 999
Press Enter to continue...:
I have tried just doing 1 day and 1 event, but same results. No errors or anything indicating a failure, so not sure how to troubleshoot this. I have had similar issues with switches in the past that were resolved with some researching into scopes, but I don't think this is the same case as it is all self contained within the switch itself.
I am at a loss, any ideas? As always, any insight into my script is greatly appreciated, even if it doesn't resolve the problem at hand.
JosefZ has provided the crucial pointer:
force synchronous to-display output with, such as with Out-Host
if you neglect to do so, the pause statement will - surprisingly - execute before the [pscustomobject] instances emitted by the foreach statement, due to the asynchronous behavior of the implicitly applied Format-Table formatting - see this answer for details.
Here's a simplified example:
switch ('foo') {
default {
# Wrap the `foreach` statement in . { ... },
# so its output can be piped to Out-Host.
. {
foreach ($i in 1..3) {
[pscustomobject] #{ prop = $i }
}
} |
Out-Host # Without this, "pause" will run FIRST.
pause
}
}
Note:
For Out-Host to format all output together it must receive all output from the foreach loop as part of a single pipeline.
Since foreach is a language statement (rather than a command, such as the related ForEach-Object cmdlet) that therefore cannot directly be used at the start of a pipeline, the above wraps it in a script block ({ ... }) that is invoked via ., the dot-sourcing operator, which executes the script block directly in the caller's context and streams the output to the pipeline.
This limitation may be surprising, but is rooted in the fundamentals of PowerShell's grammar - see GitHub issue #10967.
An all-pipeline alternative that doesn't require the . { ... } workaround would be:
1..3 |
ForEach-Object {
[pscustomobject] #{ prop = $_ } # Note the automatic $_ var.
} |
Out-Host
I am checking if SMB version 1 is enabled or not on my Windows Server 2008 R2.
Even though SMB1 is enabled and running, when I am doing a if loop comparison if SMB1 is running then it is executing the else condition
Can anyone please tell me where I am going wrong?
Here is my script :
$SMBVersionRunning = sc.exe query mrxsmb10
$SMBVersionState = $SMBVersionRunning | Select-String -Pattern "STATE"
$SMBRunningStatus = $SMBVersionState | Select-String -Pattern "RUNNING"
if( $SMBVersionRunning.Contains($SMBRunningStatus.ToString()) -eq 0)
{
Write-Host "SMB1 is enabled"
}
else
{
Write-Host "SMB1 is not enabled"
}
Using Get-Service allows a simpler and more robust solution:
if ((Get-Service mrxsmb10).Status -eq 'Running') {
"SMB1 is enabled"
}
else {
"SMB1 is not enabled"
}
If there's a chance that the service isn't even installed, add -ErrorAction Ignore (PSv5+) or -ErrorAction SilentlyContinue to the Get-Service call in order to silence error output.
If you do want to stick with the string-parsing approach, you can simplify your attempt to the following:
if ((sc.exe query mrxsmb10) -match 'STATE' -match 'RUNNING') {
"SMB1 is enabled"
}
else {
"SMB1 is not enabled"
}
While probably not necessary in this case, you could make the matching stricter by looking for the search terms only as full words, by enclosing them with \b...\b; i.e., \bSTATE\b and \bRUNNING\b
As for what you tried:
if ($SMBRunningStatus) ... should give you what you want, because it will only be nonempty if both the string STATE and RUNNING were found on the same line, and a nonempty variable is "truthy" in a conditional (in any Boolean context).
$SMBVersionRunning.Contains($SMBRunningStatus.ToString()) doesn't work as intended, because it is not a string operation, but an array containment operation, given that invocation of an external program such as sc.exe query mrxsmb10 returns an array of lines.
That is, the method call would only return $True if $SMBRunningStatus.ToString() matched a line in full.
Aside from that, your logic of comparing to -eq 0 (a) compares the Boolean result from .Contains() to an [int] and (b) has the logic accidentally reversed; to test if a Boolean result is $True, you can use -eq $True, but that isn't necessary: simply use the Boolean result as-is; similarly, to test for $False you can use the -not operator instead of -eq $False.
I am attempting to query AD to get the phone number for a user then format it to a standard format (###-####). I'm using a Switch statement because I've seen the number in a handful of different formats. With the way I have my code set up though I am getting an error: "The term Switch is not recognized as a name of a cmdlet, function, script file..."
Here's the code:
$ADInfo = Get-ADUser $User.Email.split("#")[0] -Properties * -Server $DC
$User.'Phone Number' = $ADInfo.telephoneNumber | Switch -regex ($_) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
break
}
'^\d{7}$'
{
"{0:###-####}" -f $_
break
}
default
{
break
}
}
Am I misunderstanding how the pipeline works? I suppose I could just save this info to a temp variable and then enter a Switch statement but this seemed like an effective way to use the pipeline.
Anyhow, any help is appreciated! Thanks!
Am I misunderstanding how the pipeline works?
Yes.
The pipeline can only pipe stuff to commands - and switch is not a command, it's a language keyword.
You can wrap your switch statement in a ForEach-Object block and pipe the input to that:
$User.'Phone Number' = $ADInfo.telephoneNumber | ForEach-Object {
Switch -regex ($_) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
}
'^\d{7}$'
{
"{0:###-####}" -f $_
}
}
}
As Ansgar points out, the break statements here are redundant (and not required), since your test cases are mutually exclusive
As suggested by Mathias R. Jessen, here's an example of how to use Switch without having to iterate through things with a ForEach loop, or piping anything to it.
Switch will iterate an array on its own, so there's no need to nest it in a loop, or even pipe anything in this case. It can be done as such:
$ADInfo = Get-ADUser $User.Email.split("#")[0] -Properties * -Server $DC
$User.'Phone Number' = Switch -regex ($ADInfo.telephoneNumber) {
'^\d{5}$'
{
"{0:38#-####}" -f $_
}
'^\d{7}$'
{
"{0:###-####}" -f $_
}
}
Aside from that I would recommend using Continue instead of Break. Further example of when continue would be used within the Switch scriptblock:
$TestData = #('John.Doe#Company.Com','JDoe','(555)867-5309','Taco Tuesday')
Switch -regex ($TestData){
'#' {"$_ is an email address.";continue}
'^[+()\-\d\s]+$' {"$_ is a phone number.";continue}
default {"$_ is unknown."}
}
Here we have something being done in the default block, so we want to include a continue statement in the previous cases so that if a match is found it moves to the next item in the array, and does not execute the default case. The output of that would be:
John.Doe#Company.com is an email address.
JDoe is unknown.
(555)867-5309 is a phone number.
Taco Tuesday is unknown.
How can I script "Does String -contains -not _" / "Does the string contain anything other than _"?
I'm not stuck as I've found a good enough work around. More curiosity than anything else.
Example:
$String = 1,1,1,2,5
$String -contains !(1)
This always comes up False
My solution at the moments is to remove the 1's and see if it's null like so:
$String2 = $String -ne 1
if ([String]::IsNullOrEmpty($String2)) {
Write-Host "True"
} else {
Write-Host "False"
}
Real World Example:
My script is designed to try a certain action until it works. In this case get-msoluser.
At the end of my script I want to count any errors (and list them later) but there will always be an error listed for "get-msoluser" as it fails until it works. So I'm trying to not include that certain error in the count.
$Errors = $Error.InvocationInfo.MyCommand.Name
if ($Errors -contains !("get-msoluser")) {
Write-Host "There was an error I actually care about"
}
INSTEAD I have to do this:
$Errors = $Error.InvocationInfo.MyCommand.Name
$ErrorsICareAbout = $Errors -ne "get-msoluser"
if ([String]::IsNullOrEmpty($ErrorsICareAbout)) {
Write-Host "$ErrorsICareAbout.Count"
} else {
Write-Host "There were errors you actually cared about"
}
Am I missing something that's right under my nose?
You simply need to use -notcontains or add the not operator around then entire -contains comparison like this:
If ($Errors -notcontains ("get-msoluser"))
or
If (!($Errors -contains ("get-msoluser")))
Rather than filtering out the error, try not producing an error in the first place. To suppress errors from a particular command, you can set the error action to SilentlyContinue.
Write-Error 'fail' -ErrorAction SilentlyContinue
So in the case of retrying until Get-MsOlUser works, you could use something like
while($msolUser -eq $null) {
$msolUser = Get-MsOlUser ... -ErrorAction SilentlyContinue
#Wait a second before retrying.
Start-Sleep -Seconds 1
}
#Now work with $msolUser
(You probably also want to put an upper limit on the number of retries)
I'm currently creating an AD script that can get the AD groups of one machine and transfer them to a new machine (in case of system failure).
I've managed to get the script to go out and find the versions of Windows that the two machines are running via their hostname, however I'm having a problem creating an 'if' statement to compare the two versions of Windows.
The idea is that if the are the same version (and thus the same package version) the groups will be copied automatically, but I can't for the life of me figure out how to do it.
Please consider this code:
function W_version_current
{
$current = Get-WmiObject Win32_OperatingSystem -computer $current_hostname.text | select buildnumber
if ($current -match '7601')
{
"Windows 7"
}
elseif($current -match '2600')
{
"Windows XP"
}
elseif($current -eq $null)
{
"The box is empty"
}
else
{
"Function not supported"
}
}
function W_version_target
{
$target = Get-WmiObject Win32_OperatingSystem -computer $target_hostname.text | select buildnumber
if ($var -match '7601')
{
"Windows 7"
}
elseif($target -match '2600')
{
"Windows XP"
}
elseif($target -eq $null)
{
"The box is empty"
}
else
{
"Function not supported"
}
}
function compare_current_target
{
if(W_version_current -eq W_version_target)
{
"Matching version of Windows detected"
}
else
{
"Versions of Windows do not match"
}
}
Now is it true that all variables cannot be accessed outside of functions?
If so, what else can I do?
Probably what you're missing is that with PowerShell order of operations, you often have to put function calls in parentheses.
Try this instead:
if ((W_version_current) -eq (W_version_target))
{
"Matching version of Windows detected"
}
else
{
"Versions of Windows do not match"
}
To answer your question, scope in PowerShell works pretty much like most other scripting languages, e.g. variables declared in functions cannot be used outside of the functions they were declared, unless you declare them as global, which you can do like so:
$global:x = "hi"
You can then use the variable $x anywhere, or if you like, $global:x, and it will have the value of "hi".