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:
-
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:
-
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/en-us/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 Get-DhcpServerv4Lease 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: http://technet.microsoft.com/en-us/library/jj590737.aspx
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: http://technet.microsoft.com/en-us/library/hh849715.aspx
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: http://technet.microsoft.com/en-us/library/hh849895.aspx
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.
-
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: http://technet.microsoft.com/en-us/library/hh847813.aspx
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
-
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 ENTER-PSSESSION or INVOKE-COMMAND cmdlets.
The TrustedHosts item can contain a comma-separated list of computer names, IP addresses, and fully-qualified 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: http://technet.microsoft.com/en-us/library/hh847850.aspx
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 Telnet-Client }. 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: http://technet.microsoft.com/en-us/library/hh849719.aspx
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($[, $]...) } -ArgumentList -or-
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: http://technet.microsoft.com/en-us/library/hh849798.aspx
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.
-
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.
-
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.
-
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.
Share with your friends: |