Powershell How To Series – Server Core Role Creation

advertisement
Powershell How-To Series Project: Core Server Role installation
The overall process that we will automate is the provisioning of a Server Core computer that is running
Microsoft® Windows Server® 2012. I am assuming that the computer already has the operating system
installed, and a local Administrator password has been set. Everything else must be automated by a
script that you will write.
To make your script more easily reused, you will write it as a parameterized script. Certain values within
the script will remain hard-coded, such as those values that will rarely change. Those values include the
name of a DHCP server, your domain name, and other values that are fairly static in any environment.
You will parameterize the values that will change every time that a new server is provisioned. Those
values may include the following:





The computer’s new name
The computer’s new IP address
The computer’s physical network adapter media access control (MAC) address
The role or roles to install on the computer
The credentials that are used to access the computer and the domain
The high level steps we are going to accomplish with this script are as follows:
1.
2.
3.
4.
5.
6.
7.
8.
Create a parameterized script
Get the DHCP assigned IP address of the Server Core computer
Get the MAC address of the Server Core computer
Create a DHCP reservation for the Server Core computer
Modify the local TrustedHosts list
Add a role to the Server Core computer
Add the Server Core computer to the Domain
Reset the TrustedHosts list value
Assumptions for this project:
1.
2.
3.
4.
We are going to use the POWERSHELL ISE console for the creation of this script.
We will start the ISE console with ADMINISTRATOR privileges.
We will save our script to the following path once we start creating it: C:\Scripts
We will name our script as follows: Set-ServerCoreInstance.ps1
In order to run PowerShell as an administrator, you will need to do the following in most cases:
Below is the entire script, as it will look when you are done. I am starting with the entire script
completed for you so that you can see what it looks like, and can examine it, by section, to understand
how it operates.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$MACAddress,
$LocalCredential = (Get-Credential -Message "Provide credential for target machine"),
$DomainCredential = (Get-Credential -Message "Provide domain credential to add machine to domain"),
[Parameter(Mandatory=$True)]
[string]$NewComputerName,
[Parameter(Mandatory=$True)]
[string]$NewIPAddress,
[Parameter(Mandatory=$True)]
[string]$Role,
[string]$Domain = "ADAM",
[Parameter(Mandatory=$True)]
[string]$ScopeID,
[Parameter(Mandatory=$True)]
[string]$DHCPServerName
)
$OldIPAddress = Get-DhcpServerv4Lease -ScopeId $ScopeID -ComputerName $DHCPServerName |
Where-Object { $PSItem.ClientId -eq $MACAddress } | Select-Object -ExpandProperty IPAddress |
Select-Object -ExpandProperty IPAddressToString
$OldIPAddress = "$OldIPAddress"
# Add a reservation
Add-DhcpServerv4Reservation -ClientId $MACAddress `
-IPAddress $NewIPAddress -ScopeId $ScopeID `
-ComputerName $DHCPServerName
# Save TrustedHosts
$OriginalTrustedHosts = Get-Item WSMan:\localhost\Client\TrustedHosts |
select -ExpandProperty value
# Set TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $OldIPAddress
# Install role
Invoke-Command –ComputerName $OldIPAddress `
–Credential $LocalCredential `
–ScriptBlock { Install-WindowsFeature Telnet-Client }
# Add to domain and rename
Invoke-Command –ComputerName $OldIPAddress `
–Credential $LocalCredential `
–ScriptBlock { param($x,$y) Add-Computer –DomainName ADAM `
–NewName $x `
–Credential $y `
–Restart } `
–ArgumentList $NewComputerName,$DomainCredential
# Restore TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value “$OriginalTrustedHosts”
Let’s break down what is happening in the script so that you are able to understand the functionality
being used by section to achieve our end result, which is to successfully provision the Core Server
instance.
First section:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$MACAddress,
$LocalCredential = (Get-Credential -Message "Provide credential for target machine"),
$DomainCredential = (Get-Credential -Message "Provide domain credential to add machine
to domain"),
[Parameter(Mandatory=$True)]
[string]$NewComputerName,
[Parameter(Mandatory=$True)]
[string]$NewIPAddress,
[Parameter(Mandatory=$True)]
[string]$Role,
[string]$Domain = "ADAM",
[Parameter(Mandatory=$True)]
[string]$ScopeID,
[Parameter(Mandatory=$True)]
[string]$DHCPServerName
)
The place to start is with [CmdletBinding()]. If you are not familiar with this, it is an attribute of functions
that makes them operate like compiled cmdlets that are written in C#, and it provides access to features
of cmdlets. Windows PowerShell binds the parameters of functions that have the CmdletBinding
attribute in the same way that it binds the parameters of compiled cmdlets. WOW !!!, techspeak … ( the
full description of this can be found here: http://technet.microsoft.com/en-us/library/hh847872.aspx
So, what does this really mean for us? Well, it means the following in most cases:
Basically, it turns on cmdlet-style parameter binding capabilities, either for a script or for a function. You
get the following capabilities with it:




The ability to add [Parameter()] decorators to parameters - see
“about_functions_advanced_parameters" in PowerShell help for more detail.
The ability to use Write-Verbose and Write-Debug in your script or function, and have their
output controlled by -Verbose and -Debug parameters of that script or function. You do not
need to declare those parameters - [CmdletBinding()] adds them.
Your script or function picks up the other common parameters, too, like -EV and -EA (see
"about_common_parameters" in Powershell help for more detail).
The ability to have -whatif and -confirm added to your script or function.
The Param( section is where you declare whatever parameters you will want to use with the script. If
you are going to use parameters in the script, as we are doing, then they follow the Param( statement,
and are listed as shown, separated by a comma “,” between each parameter being declared. The last
parameter DOES NOT have a comma after it, signifying that it is the last in the series, which is followed
by a “)” signifying that the Param( declaration section is finished and is being closed. For example:
[Parameter(Mandatory=$True)]
[string]$MACAddress,
[Parameter(Mandatory=$True)]
[string]$DHCPServerName
)
Some additional things to be aware of in this area:
[Parameter(Mandatory=$True)]
[string]$MACAddress,
When [Parameter(Mandatory=$True)] is used, you are setting the parameter that will follow to be a
mandatory parameter, meaning that it must have a value provided, or the script will not continue.
In addition, the $True tells PowerShell to ignore the default for whatever parameter is declared
following the statement.
The [string] $MACAddress, statement declares the $MACAddress variable as a string variable, BUT, and
this is important, DOES NOT declare a value for the variable at this time. This means that we will be
prompted to provide a value for the $MACAddress variable when the script executes, or that we will
have to execute the script in such a way that we provide the value as part of the execution parameters
… More on this later. 
One final thing to discuss in this section:
$LocalCredential = (Get-Credential -Message "Provide credential for target machine"),
$DomainCredential = (Get-Credential -Message "Provide domain credential to add machine
to domain"),
There are two variables being declared above, $LocalCredential and $DomainCredential. Each is being
set = to the following cmdlet, Get-Credential, and we are then using the –Message parameter to
provide a specific message to the user running the script, on-screen, when the script executes.
See the following for more on the Get-Credential cmdlet: http://technet.microsoft.com/enus/library/hh849815.aspx
Second section:
$OldIPAddress = Get-DhcpServerv4Lease -ScopeId $ScopeID -ComputerName $DHCPServerName
| Where-Object { $PSItem.ClientId -eq $MACAddress } | Select-Object -ExpandProperty
IPAddress | Select-Object -ExpandProperty IPAddressToString
$OldIPAddress = "$OldIPAddress"
Here we start off by declaring a variable called $OldIPAddress. We are setting this variable = to the GetDhcpServerv4Lease cmdlet, and we are using the parameter –ScopeID with the $ScopeID variable to
provide it’s value for the -ScopeID parameter. We are doing the same the –ComputerName parameter,
using the $DHCPServerName variable to provide it’s value for the –ComputerName parameter.
See the following for more on the Get-DhcpServerv4Lease cmdlet:
us/library/jj590737.aspx
http://technet.microsoft.com/en-
The ending output of this part of our operation will be to display a list of all address leases in the scope
value that was provided via the $ScopeID variable.
This will be important, as it will provide the input for the next step in our operation.
Next, we pipe “|” the output from the $OldIPAddress variable to the Where-Object cmdlet. Piping
output simply means that we are taking the output from the previous item, and are using it as the input
for the next operation(s) that we are going to engage in.
By piping the output of the list of all address leases currently active on the DHCP server in the scopeid
we are querying against, we will now have the ability to tell our script to pick the address of a certain
machine from that list and to then take the information associated with that choice and store it for
future use.
Where-Object { $PSItem.ClientId -eq $MACAddress } | Select-Object -ExpandProperty
IPAddress | Select-Object -ExpandProperty IPAddressToString
The Where-Object cmdlet is being used to specify that where the object’s value we are providing is = to
something, then we will execute some action.
Specifically, we are saying Where-Object is = to the value of the Get-DhcpServerv4Lease cmdlet
parameter ClientID, set to the value of the variable $MACAddress.
** A note here for clarification purposes: $PSItem is the same as $_. The goal with the introduction of
the $PSItem variable is making the code containing the “current object in the pipeline” easier to read
and understand. **
See the following for more on the Where-Object cmdlet:
us/library/hh849715.aspx
http://technet.microsoft.com/en-
We then pipe the output of the Where-Object cmdlet operation to the Select-Object cmdlet operation.
The Select-Object cmdlet does just what the name implies, it selects objects, or object properties, and
then allows us to do something with them.
Here we are using the Select-Object cmdlet with the –ExpandProperty parameter and a string value = to
IPAddress to specifically select the IP Address of the computer we identified in the previous step by
using the Where-Object cmdlet to select the specific computer that was equal to the $MACAddress
variables value provided.
Then we are piping that output to the next Select-Object cmdlet with the –ExpandProperty parameter
and a string value = to IPAddressToString, in order to have the IP Address value available to us as a
string, which we consume in the next step.
Now we convert the IP Address to a string object by doing the following:
$OldIPAddress = "$OldIPAddress"
See the following for more on the Select-Object cmdlet:
us/library/hh849895.aspx
http://technet.microsoft.com/en-
Third section:
# Add a reservation
Add-DhcpServerv4Reservation -ClientId $MACAddress `
-IPAddress $NewIPAddress -ScopeId $ScopeID `
-ComputerName $DHCPServerName
Whenever we see a “#” item in a script, it simply means that we will ignore anything that comes after on
the same line. We use this as a way to add basic descriptions to the script sections so that we know
what is about to happen.
We are using the Add-DhcpServerv4Reservation cmdlet here to create a DHCP server reservation for
the Server Core instance that we want to target later in the script. The reservation will be built using the
parameter values provided by the variables, as noted. The reservation will be created on the computer
identified in the –ComputerName parameter’s variable $DHCPServerName.
See the following for more on the Add-DhcpServerv4Reservation cmdlet:
http://technet.microsoft.com/en-us/library/jj590686.aspx
Fourth section:
# Save TrustedHosts
$OriginalTrustedHosts = Get-Item WSMan:\localhost\Client\TrustedHosts |
select -ExpandProperty value
We are declaring that the $OriginalTrustedHosts variable is equal to the result of the Get-Item cmdlet.
The Get-Item cmdlet is being asked to specifically get the WSMan provider and to then access the
current TrustedHosts list and save it to the variable $OriginalTrustedHosts.
We have two new concepts that need to be defined here.
A. PowerShell Providers …. What are they?
A Windows PowerShell provider is a .NET program that allows the data in a specialized data store to be
accessible in Windows PowerShell.
The data, which appears in a temporary drive for the session, can be managed with built-in or custom
cmdlets.
Providers may also be referred to as snap-ins.
The WSMan provider for Windows PowerShell lets you add, change, clear, and delete WS-Management
configuration data on local or remote computers.
The WSMan provider exposes a Windows PowerShell drive with a directory structure that corresponds
to a logical grouping of WS-Management configuration settings. These groupings are known as
containers.
See the following for more on the WSMan provider:
us/library/hh847813.aspx
http://technet.microsoft.com/en-
You can get a complete list of the PowerShell Providers available to you in your system by
running the following command in PowerShell:
Get-PSProvider
B. The TrustedHosts List …. What is it? Why do we use it?
TrustedHosts is a setting on the local computer that defines one or more computers that are trusted
when establishing remoting sessions using WinRM from the local computer using either the ENTERPSSESSION or INVOKE-COMMAND cmdlets.
The TrustedHosts item can contain a comma-separated list of computer names, IP addresses, and fullyqualified domain names. Wildcards are permitted.
Only members of the Administrators group on the computer have permission to change the list of
trusted hosts on the computer. Caution: The value that you set for the TrustedHosts item affects all
users of the computer.
See the following for more on the TrustedHosts List:
us/library/hh847850.aspx
http://technet.microsoft.com/en-
If the WinRM Service is not running on the local machine, you will receive a prompt to start it, as shown
below:
Simply select Yes to start the service and then the script will continue. You could start the WinRM
service prior to running the script as well. The WinRM service is set to a manual start value by default.
Fifth section:
# Set TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $OldIPAddress
We are using the Set-Item cmdlet to set the WSMan provider item TrustedHosts to a parameter –value
of the value $OldIPAddress.
Sixth section:
# Install role
Invoke-Command –ComputerName $OldIPAddress `
–Credential $LocalCredential `
–ScriptBlock { Install-WindowsFeature Telnet-Client }
We are using the Invoke-Command cmdlet with the –ComputerName parameter set to the value of the
$OldIPAddress variable. This allows us to specify that whatever coammnds we will want to run
following this cmdlet, we want to run them against a remote computer, specifically the remote
computer identified in the –ComputerName parameter value.
We are then using the –Credential parameter with a value of variable $LocalCredential. This will put up
an on-screen prompt for the user executing the script, asking them to provide a username and
password.
We are then using the –ScriptBlock parameter, followed by { Install-WindowsFeature TelnetClient }. The use of the –ScriptBlock parameter is required with the Invoke-Command cmdlet, and is
used to specify what command(s) to execute on the computer(s) being targeted by the cmdlet. The
syntax for the –ScriptBlock parameter requires all commands to be placed inside of the curly braces, as
shown.
In this case, we are using the –ScriptBlock parameter to call the Installation of a Windows Feature called
the Telnnet Client.
See the following for more on the Invoke-Command cmdlet:
us/library/hh849719.aspx
http://technet.microsoft.com/en-
Seventh section:
# Add to domain and rename
Invoke-Command –ComputerName $OldIPAddress `
–Credential $LocalCredential `
–ScriptBlock { param($x,$y) Add-Computer –DomainName ADAM `
–NewName $x `
–Credential $y `
–Restart } `
–ArgumentList $NewComputerName,$DomainCredential
Here we are using the Invoke-Command cmdlet again, along with the –ComputerName parameter. We
are also using the –Credential parameter with the $LocalCredential variable again.
The new and interesting stuff starts with the –ScriptBlock parameter that we are declaring. If you look
carefully at what follows, you will see something different that we did not do with the –ScriptBlock the
last time we used it. Let’s take a closer look and figure out what we are doing here:
–ScriptBlock { param($x,$y) Add-Computer –DomainName ADAM `
–NewName $x `
–Credential $y `
–Restart } `
–ArgumentList $NewComputerName,$DomainCredential
When using the –ScriptBlock parameter, we need to remember the following information:
What do we already know from before?
The –ScriptBlock parameter specifies the commands to be run. You must enclose the commands in
braces ( { } ) to create a script block. This parameter is required.
What do we need to add now?
By default, any variables in the command are evaluated on the remote computer. To include local
variables in the command, use the -ArgumentList parameter.
What else do we need to add to this to make it all come together and make sense?
The –ArgumentList parameter supplies the values of local variables in the command. The variables in
the command are replaced by these values before the command is run on the remote computer. Enter
the values in a comma-separated list. Values are associated with variables in the order that they are
listed.
The values in ArgumentList can be actual values, such as "1024", or they can be references to local
variables, such as "$max".
To use local variables in a command, use the following command format:
{param($<name1>[, $<name2>]...) <command-with-local-variables>} -ArgumentList <value> -or- <localvariable>
The "param" keyword lists the local variables that are used in the command. The -ArgumentList
parameter supplies the values of the variables, in the order that they are listed.
So what does it all mean when we bring all of the explanations together? Let’s see:
–ScriptBlock { param($x,$y) Add-Computer –DomainName ADAM `
–NewName $x `
–Credential $y `
–Restart } `
–ArgumentList $NewComputerName,$DomainCredential
Breaking it down as we go, let’s start with the first line:
This is going to use the –ScriptBlock parameter to specify what command(s) should be run against the
machine we are targeting. The addition of the param keyword followed by the ($x,$y) variable
declaration tells PowerShell that the local variables to use for this command will be $x and $y, and that
they will be given values via the –ArgumentList parameter to follow.
This is followed by the Add-Computer cmdlet, and the –DomainName parameter with a value provided
of ADAM.
This adds the computer targeted to the Domain specified.
See the following for more on the Add-Computer cmdlet:
us/library/hh849798.aspx
http://technet.microsoft.com/en-
The second line uses the –NewName parameter of the Add-Computer cmdlet and sets it’s value to the
$x variable. The –NewName parameter is what the name of the computer being added to the domain
will be set to once it is successfully added.
The third line uses the –Credential parameter of the Add-Computer cmdlet and sets it’s value to the $y
variable. The –Credential parameter is used to specify the credential to be used that has the rights
necessary to join the computer being targeted to the domain identified.
The default value is the current user. Type a user name, such as "Adam12" or "Adam\Adam12", or enter
a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, you
will be prompted for a password.
The fourth line uses the –Restart parameter of the Add-Computer cmdlet. This will restart the targeted
computer being added to the domain once the operation completes.
The fifth line uses the –ArgumentList parameter to specify the locally declared variables to be used by
the param keyword, and the order to provide them in. In this case, we are declaring that we want to use
the local variables that we declared all the way back in the beginning of the script, as shown below:
$DomainCredential = (Get-Credential -Message "Provide domain credential to add machine
to domain"),
[Parameter(Mandatory=$True)]
[string]$NewComputerName,
We are passing them through the –ArgumentList parameter in the order noted below:
–ArgumentList $NewComputerName,$DomainCredential
This means that when the param keyword parses them and replaces $x,$y with the values being
provided through the –ArgumentList parameter, that the outcome will be as follows:
–NewName $x = $NewComputerName
–Credential $y = $DomainCredential
We do not actually see the above statement appear anywhere in the script, because PowerShell hides it
from us, and just processes the logic of the outcome, and then applies the resultant outcome(s) to the
remaining activities if needed. I am walking us through the process step-by-step to illustrate for you how
it works, so that you are able to understand the detail surrounding the cmdlet and parameter choices
that we have made in this section of the script.
Eighth section:
# Restore TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value “$OriginalTrustedHosts"
We are using the Set-Item cmdlet with the WSMan provider to specify that the –Value parameter for the
TrustedHosts item should be set to whatever the value of the $OriginalTrustedHosts variable is.
Now that we have dissected the entire script, examining what each element does, we will want to test
the script to ensure that it works correctly, and does what we expect it to do.
In order to test the script, do the following:
In the ISE Console pane, type, then run:
C:\Scripts\Set-ServerCoreInstance –NewComputerName ADAM-CORESVR4 –NewIPAddress
192.168.0.123 –Role Telnet-Client –MACAddress D0-67-E5-24-C1-DC –ScopeID 192.168.0.0 –
DHCPServerName ADAM-DC1
(You will have to substitute the appropriate values for whatever your test environment
requires !!!)
Before we get to what this will actually do, and what to expect when it works, let’s discuss one final item
that will be very important for you to be aware of.
What exactly are we doing with our test?
C:\Scripts\Set-ServerCoreInstance –NewComputerName ADAM-CORESVR4 –NewIPAddress
192.168.0.123 –Role Telnet-Client –MACAddress D0-67-E5-24-C1-DC –ScopeID 192.168.0.0 –
DHCPServerName ADAM-DC1
This string allows us to test our Set-ServerCoreInstance.ps1 script. However, it is the way that it allows
us to carry out that test that is interesting for us to take note of here.
We are providing the values for each of the parameters we declared in the beginning of the script in our
Param( block that we did not provide a value for initially. ( If you look carefully you will see that the only
parameter we declared a value for initially was the $Domain variable, which we set = to “ADAM”. )
Take a look back at the beginning of the script:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$MACAddress,
$LocalCredential = (Get-Credential -Message "Provide credential for target machine"),
$DomainCredential = (Get-Credential -Message "Provide domain credential to add machine
to domain"),
[Parameter(Mandatory=$True)]
[string]$NewComputerName,
[Parameter(Mandatory=$True)]
[string]$NewIPAddress,
[Parameter(Mandatory=$True)]
[string]$Role,
[string]$Domain = "ADAM",
[Parameter(Mandatory=$True)]
[string]$ScopeID,
[Parameter(Mandatory=$True)]
[string]$DHCPServerName
)
Now that is pretty cool, and here’s why. If we did not run our script this way, then the user that executes
the script would be faced with the following issue, they would be prompted for the input values for each
parameter declared in the Param( block, and the script would not run successfully until ALL of the values
had been provided by the user, one at a time, in response to each prompt from PowerShell as the script
executes.
You can see what that will look like by examining the picture below:
Wait for the script to complete, and then wait a few minutes for the Server Core instance to restart.
Note: It is expected that the target computer will restart and you will loose your connection. Your
workstation is attempting to connect to the old IP Address and will not successfully reconnect as the IP
Address has been changed to the new address. In the console prompt you will see an error message
saying “..Connection Lost..” attempting to reconnect. You will then receive a WinRM Security
Configuration prompt; press Y to confirm, after which the script will stop running.
A. To verify that the computer is in the domain, on your workstation with the Windows PowerShell
console run:
Get-ADComputer –filter *
Confirm that ADAM-CORESVR4 is listed.
B. To verify the role installation in the Windows PowerShell ISE, run:
Get-WindowsFeature –ComputerName ADAM-CORESVR4
Confirm that the Telnet-Client role is listed as Installed.
C. To verify the TrustedHosts list, run:
Dir WSMan:\localhost\Client
Confirm that the TrustedHosts item is empty.
This ensures that the TrustedHosts value has been returned to its original state, which is most likely
empty, after been changed to the $OLDIPAdddress variable i.e. the original IP address of the
machine. If the IP address were left in the TrustedHosts item this could be a security
vulnerability.
And there you have it. A complete PowerShell v3.0 / v4.0 compatible script that will take an existing
Microsoft Windows Server 2012 core instance and reconfigure it and join it to an existing domain.
We have broken down the entire script and examined it piece by piece, explaining along the way what
all of the elements do.
We have also examined how to test the script to ensure it will run correctly, and how to do that by
providing the required parameter values within the test string, so as to avoid being prompted for them
in-line in the console when the script starts to execute.
Download