How to NOT Have Redundancy

If I want to create an advanced function where a $ComputerName parameter can have either the name of a computer obviously, or where it can have another value like “ComputerList” that would indicate using a text file that would open up so the user can input multiple computer names, how would I not create redundant code for each value?

For example,

Function TestFunction {
[CmdletBinding()]
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$ComputerName
)

If ($ComputerName -eq "ComputerList)
{
$Computers = Get-content “$env:Temp\computerlist.txt”

 Foreach ($Computer in $Computers)
 {
      #Do stuff here
 }

Else
{
#do stuff here

 #Will be nearly identical to the code in the "if" block above except without a get-content

}

Is there a way to just have only one block process the two different values so as not to reate redundancy and not give me carpal tunnel from all the typing?

Thanks and as always, I appreciate your help

There are several good answers.

My personal preference would be to rig your computer name parameter to accept pipeline input. That way it doesn’t worry where the names come from. Could be a text file, could be AD, could be a database. That way you’re not having to add a new parameter everytime some new kind of input comes along. That fits the PowerShell model.

You can already use a parenthetical:

-computername (GC names.txt)

Again, working with the shell standard patterns. Don’t code for specific input, let the shell handle it.

To explicitly answer your question, although it’s not what I’d do, you could add a parameter that accepts a file and. Place it and -computername in different parameter set names, and they’ll be mutually exclusive. But I’d still build the code to avoid duplicated code inside the function.

Which would you like to pursue?

I want to do whatever follows best practices and less typing. The reason I like to use the -ComputerName “ComputerList” is because I like to tell PowerShell to create and open up a blank ComputerList.txt file located within the user’s Temp dir and then they can fill it out, save and close it. Then they will just click on an “OK” button on some pop up box to proceed. Then PowerShell will begin processing the the ComputerList.txt file. I have done this in my other scripts, but I keep having to use redundant code and it’s getting really old. I want to start utilizing Advanced Functions more, but I new to them so this is a learning curve for me. Would it be possible to give me an example how I would go about doing what you mentioned above as in “rig your computer name parameter to accept pipeline input”?

Thanks Don

So, the idea is to separate those things as much as possible. For example, the idea of opening a blank file, collecting names in a GUI, something like that - it’s good, but you’d want that ability in a LOT of potential situations. So you make it a standalone function. It outputs whatever it collects to the pipeline, which can then be sent on to the next command. E.g.,

Function Get-Stuff { [CmdletBinding()] Param( [Parameter(Mandatory=$True,ValueFromPipeline=$True)] [string[]]$ComputerName ) PROCESS { foreach ($computer in $computername) { # do something to $computer } } }

Now that function can be used in a lot of ways.

Get-Content names.txt | Get-Stuff
Get-Stuff -Computername (Get-Content names.txt)
Get-ADComputer -filter * | Select -Expand Name | Get-Stuff

But Get-Stuff doesn’t worry about HOW the input is being collected. So maybe you write a function called “Show-ComputerNameGUI” (the verb “show” implies that this is interacting with the screen and requires human intervention). It has a text box where people can punch in computer names and click OK, and it outputs those computer names to the pipeline.

Show-ComputerNameGUI | Get-Stuff

That way you’re also not putting stuff in a temp file. No need to use text files as intermediate storage - the whole point of the pipeline is to act as intermediate storage between commands.

You build these things the right way, each function does ONE thing, and they snap together like LEGOs. Pick up “Learn PowerShell Toolmaking in a Month of Lunches,” if you like, it’s really what the whole book is about.

Hope that helps.

DAMN Don! You reply fast dude, that’s awesome. I bought all your books, actually, just this year back in January. I browse through them when I have the time, but I have tons of projects I work on simultaneously and a new job that I accepted as a Microsoft SCCM Engineer for a company in Oregon and I am currently in Alaska right now, so time is something that I simply don’t have much of at this point. Not to mention I have a second child on the way due on April 4th during the time that I have to prepare for this colossal move back to the Lower 48. So needless to say, it’s nuckin’ futs right now. This is why I use this forum, it’s great. The cool thing is that at least when I buy the books that you and many of your associates have written on PowerShell for Manning Publishing, I can also get the PDF version totally “free” off their website by punching in a few alphanumeric codes that are listed in the cells on the attached paper that accompanies the books. The only problem is that I packed up all my books already and the ONLY PDF that I have not downloaded is, you guessed it, “Learn POWERSHELL TOOLMAKING IN A MONTH OF LUNCHES” that you and Jeffery wrote. I am just trying to get this script done this weekend, if possible, and off my plate.

I will go dig out the book and download the PDF, because I really want to get this done for my current employer before I move my family and I to Oregon.

Basically what I am doing is writing up a script that will find any server throughout AD that is running WSUS. I know I can use the Registry to find the WSUS that is currently being used, but I want to find a server that has WSUS installed, but where WSUS is not being utilized, if that is even possible. I could write up an SCCM SQL report to do this, but the organization consists of 4 domains with trusts, and my SCCM 2007 environment only sees its local domain due to configured boundary limits. We have implemented SCCM 2012 at the enterprise level, but it has yet to bring in all the server assets since it is still a new migration project currently in progress. This is why I am leaning on PowerShell for this.

Does this make any sense to you, or am I just blabbering?

Thanks man

Maybe a bit of both :). But it’s a weekend, that’s ok.

It’s a shame you can’t use SCCM, that being pretty much the whole point of having it! But good luck - sounds like a worthy thing to try and figure out!

Okay, my wife at least kept that box in a very accessible location in the house. I was able to retrieve it, registered it on Manning’s site, and now I downloaded the PDF. I will browse through this right after I take my 3 year old to the park. I will hit you up if I have anymore questions DJ.

Thanks again brother.

Hey Don! I went with your advice and used some code I found online to create a function that creates an input GUI instead of using a computerlist.txt and userlist.txt. I still use the computerlist.txt and userlist.txt files, but they are completely transparent to the user. The input GUI function holds the values then spews them out to the computerlist.txt and the userlist.txt files and then I just use Get-Content to read them.

You may have a better way of doing this, and if you, or someone, could offer some recommendations, that would be great.

By the way, I decided to just drop the search WSUS servers script tool and go with creating an SCCM 2012 SQL report since after checking out the server assets in the server devices collections, a good 80% or so are there.

The script tool that I am currently working on is a tool that will add or remove an AD user to an AD group and then add or remove that group to a local admin group on a computer, but contains quite a few checks to ensure proper validation of OU, AD group, and local group member existence. There was an older rinky dink script that was slapped together created for this purpose, but it had limited functionality. So, I am improving it, or attempting to.

I should be done sometime today or perhaps early tomorrow.

Can I toss that script on here and have some of you gurus check it out and let me know what you all think?

Thanks for your help Don, I appreciate it man.

I’d have you “GUI function” simply output computer names to the pipeline, so that they could be piped to another command without the interim text files, but that’s your decision.

You’re welcome to post your script. I’d do so as a TXT file attachment, in a new thread - once someone starts answering a thread (like I did with this one), folks tend to ignore it. A new thread gets a fresh look. Put “script review” in the subject, so people know what you’re after. I can’t promise I’ll be able to look at it myself, but possibly someone else will.

This is what I did:
“I’d have you “GUI function” simply output computer names to the pipeline, so that they could be piped to another command without the interim text files, but that’s your decision”

But I screwed up at first probably due to lack of sleep. I got it working now.

Here is that GUI that I am using by the way:

function Show-InputGUI([string]$Message, [string]$WindowTitle, [string]$DefaultText) { <# .SYNOPSIS Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.
.DESCRIPTION
Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.

.PARAMETER Message
The message to display to the user explaining what text we are asking them to enter.

.PARAMETER WindowTitle
The text to display on the prompt window's title.

.PARAMETER DefaultText
The default text to show in the input box.

.EXAMPLE
$userText = Read-MultiLineInputDialog "Input some text please:" "Get User's Input"

Shows how to create a simple prompt to get mutli-line input from a user.

.EXAMPLE
# Setup the default multi-line address to fill the input box with.
$defaultAddress = @'
John Doe
123 St.
Some Town, SK, Canada
A1B 2C3
'@

$address = Read-MultiLineInputDialog "Please enter your full address, including name, street, city, and postal code:" "Get User's Address" $defaultAddress
if ($address -eq $null)
{
	Write-Error "You pressed the Cancel button on the multi-line input box."
}

Prompts the user for their address and stores it in a variable, pre-filling the input box with a default multi-line address.
If the user pressed the Cancel button an error is written to the console.

.EXAMPLE
$inputText = Read-MultiLineInputDialog -Message "If you have a really long message you can break it apart`

nover two lines with the powershell newline character:" -WindowTitle “Window Title” -DefaultText “Default text for the input box.”

Shows how to break the second parameter (Message) up onto two lines using the powershell newline character (`n).
If you break the message up into more than two lines the extra lines will be hidden behind or show ontop of the TextBox.

#>
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms

# Create the Label.
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,10) 
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $Message

# Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox 
$textBox.Location = New-Object System.Drawing.Size(10,40) 
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText

# Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(510,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = "OK"
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })

# Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(415,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = "Cancel"
$cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })

# Create the form.
$form = New-Object System.Windows.Forms.Form 
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true

# Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)

# Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() &gt; $null	# Trash the text of the button that was clicked.

# Return the text that the user entered.
return $form.Tag

}

Then I just use Foreach to process each item like this:

$UserList = Show-InputGUI "Enter User Names Here:" "User List"

$ComputerList = Show-InputGUI “Enter Computer Names Here:” “Computer List”

Foreach ($User in $UserList)
{
#Do Something
}

Foreach ($Computer in $ComputerList)
{
#Do Something
}

Thanks again Don. Your fast replies are great brother.

Oh by the way, after I move to Oregon I was thinking about starting up a bi-weekly PowerShell MeetUp to interact with others in the Central Oregon area who enjoy PowerShell. I don’t know, just a thought.

Have a good one Don and I appreciate your help and your great books. Talk to you soon, I’m sure.