How to register the result of a PowerShell expression in a variable for a DSC? - powershell

I am trying to configure an Azure VM with Azure Automation DSC. One of the resources I want to set is DNS on the client workstation with xDnsServerAddress from xNetworking module.
The problem is that it requires an interface alias and interface aliases change on Azure VMs vary depending on deployment (mainly VMs seem to get either Ethernet or Ethernet 2).
I can query the interface name locally using the following cmdlet expression:
$Interface=Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1
$InterfaceAlias=$($Interface.Name)
I don't know, however, how to use it inside the DSC.
My DSC configuration is as below (only the relevant part):
Configuration MyDscConfig
{
Import-DscResource -ModuleName xNetworking
# place-1
Node $AllNodes.where{$_.Role -eq "Workstation"}.NodeName
{
# place-2
xDnsServerAddress DnsServerAddressSetToDc1
{
Address = '10.0.0.4'
InterfaceAlias = $InterfaceAlias
AddressFamily = 'IPv4'
Validate = $true
}
}
}
The problem is that if I place the cmdlet expression either in place-1 or place-2 the compilation job fails with:
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The term 'Get-NetAdapter' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
I assume it tries to execute Get-NetAdapter on the pull server, but I may be misinterpreting the error message.
How can I register the result of the cmdlet expression on a destination machine and register it in $InterfaceAlias variable for the xDnsServerAddress resource?

You currently cannot perform a query keep the results of the operation and use it to declare the next state (See notes at the end of the answer.)
You can work around this limitation using the documented workaround/solution from xNetworking, which will find an active ethernet adapter named Ethernet1 if it does not, it will find the first active ethernet adapter and make sure it is named Ethernet1. Then, use a resource to set the DSC server address on Ethernet1.
This is investigational, names and parameters are subject to change. The DSC team is investigating a better way to do this.
Configuration SetDns
{
param
(
[string[]]$NodeName = 'localhost'
)
Import-DSCResource -ModuleName xNetworking
Node $NodeName
{
script NetAdapterName
{
GetScript = {
Import-module xNetworking
$getResult = Get-xNetworkAdapterName -Name 'Ethernet1'
return #{
result = $getResult
}
}
TestScript = {
Import-module xNetworking
Test-xNetworkAdapterName -Name 'Ethernet1'
}
SetScript = {
Import-module xNetworking
Set-xNetworkAdapterName -Name 'Ethernet1' -IgnoreMultipleMatchingAdapters
}
}
xDnsServerAddress DnsServerAddress
{
Address = '10.0.0.4'
InterfaceAlias = 'Ethernet1'
AddressFamily = 'IPv4'
DependsOn = #('[Script]NetAdapterName')
}
}
}
Notes:
There is a question in the comments. The summary of the question is if querying turns the declarative paradigm into an imperative paradigm.
Answer:
I don't believe querying turns it into an imperative paradigm, but you
currently cannot perform a query keep the results of the operation and
use it to declare the next state.
This currently forces us into something a little further away from
declarative for the problem that I would like. My personal opinion is that we
should work with what we have and write resources that query and set a
known state. Then, use the known state through the rest of the
configuration (a form a declarative-relative, per your terminology).
The DSC team has this similar UserVoice
suggestion
we are using to track this request. Please upvote it if you think this
is a useful feature.

It seems the DSC Node Configuration (which is a MOF file) must have all the values set at the time of compilation.
As a workaround I decided to use a PowerShell script resource instead of xDnsServerAddress (some values below are hardcoded to match the example in the question):
Script DnsServerAddressSetToDc1
{
GetScript = {
Return #{
Result = [string](get-DnsClientServerAddress -InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name -AddressFamily IPv4).ServerAddresses
}
}
TestScript = {
if (([string](get-DnsClientServerAddress -InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name -AddressFamily IPv4).ServerAddresses) -eq '10.0.0.4') {
Write-Verbose "DNS server set"
Return $true
} Else {
Write-Verbose "DNS Server not set"
Return $false
}
}
SetScript = {
Set-DnsClientServerAddress `
-InterfaceAlias (Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1).Name `
-ServerAddresses 10.0.0.4 `
-Validate `
-ErrorAction Stop
}
}

Related

While Loop with Break Statement in PowerShell [duplicate]

I am trying to build my own script to check some Windows services (status and start mode) and I am facing an issue on the IF ...
For example even if the service is "Running", it will never run the code inside the IF...
let me share my code below (I am a newbie on powershell so be gentle xD)
For info, I will do more actions inside the IF and ELSE, it is just for the example.
# import computers list, 1 by line
$Computers = get-content .\computers.txt
# define variable of services we want to check
$ServiceNetbios = "netbt"
# define variable to ask credentials
$Cred = Get-Credential
# declare Function to open a session a remote computer
Function EndPSS { Get-PSSession | Remove-PSSession }
EndPSS
########################################################
# BEGINNING OF SCRIPT #
# by xxx #
# 2022-02-03 #
########################################################
# loop for each computer imported from the file
foreach ($computer in $computers) {
# show name of computer in progress
$computer
# connect remotely to the computer
$session = New-PSSession -ComputerName $computer -Credential $Cred
# check Netbios service
$StatusServiceNetbios = Invoke-Command -Session $session -ScriptBlock { Get-Service -Name $Using:ServiceNetbios | select -property * }
# Check Netbios service started or not
write-host $StatusServiceNetbios.Status
if ($StatusServiceNetbios.Status -eq 'Running')
{
Write-host "IF Running"
}
else
{
write-host "IF NOT Running"
}
EndPSS
}
and what return my script :
computername
Running (<= the variable $StatusServiceNetbios.Status )
IF NOT Running (<= the ELSE action)
Thanks you in advance for your help,
this drive me crazy and maybe this is very simple...
To complement Cpt.Whale's helpful answer, this is likely to be caused by the serialization and deserialization done by Invoke-Command:
using namespace System.Management.Automation
$service = Get-Service netbt
$afterInvokeCmd = [PSSerializer]::Deserialize(([PSSerializer]::Serialize($service)))
$service.Status -eq 'Running' # => True
$afterInvokeCmd.Status -eq 'Running' # => False
$afterInvokeCmd.Status.Value -eq 'Running' # => True
$afterInvokeCmd.Status.ToString() -eq 'Running' # => True
To put some context to my answer, this is a nice quote from about_Remote_Output that can better explain why and what is happening:
Because most live Microsoft .NET Framework objects (such as the objects that PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network.
On the local computer, PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object.
However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized, and it includes properties but no methods.
This is probably because of the way powershell creates service objects - (Get-Service netbt).Status has a child property named Value:
$StatusServiceNetbios.Status
Value
-----
Running
# so Status is never -eq to 'Running':
$StatusServiceNetbios.Status -eq 'Running'
False
# use the Value property in your If statement instead:
$StatusServiceNetbios.Status.Value -eq 'Running'
True

Providing test cases to Pester V5 test

I'm trying to write a pester test (v5) to see if various services are running on remote computers. This is what I have, which works:
$Hashtable = #(
#{ ComputerName = "computer1"; ServiceName = "serviceA" }
#{ ComputerName = "computer1"; ServiceName = "serviceB" }
#{ ComputerName = "computer2" ; ServiceName = "serviceB" }
)
Describe "Checking services" {
It "check <ServiceName> is running on <ComputerName>" -TestCases $Hashtable {
( get-service -computername $ComputerName -name $ServiceName ).status | Should -be "Running"
}
}
My question is around providing the test data to the test (i.e. the list of computer names and services). Suppose I want to add more services to this list. At the moment, I would be modifying my pester file by adding more services to $Hashtable. It doesn't feel quite right to be doing this to me, and I'd like to get the approach correct at this early stage. My gut tells me that the list of services should be separated from the pester file. Then running the test would involve importing the list of services somehow. Does anyone know if I am going about this the wrong way?
Thanks for any help
Andrew
If the list of servers and services will change often, it would be a good idea to read it from a separate file, especially if you have the tests under version control. This way you can easily see in the history that only the test data has changed, but the test logic didn't.
A good file format for the given test data would be CSV:
ComputerName, ServiceName
computer1, serviceA
computer1, serviceB
computer2, serviceB
You can read the CSV using Import-Csv, but you have to convert each row to a hashtable, because Pester expects an array of hashtables for the -TestCases parameter. Import-Csv outputs an array of PSCustomObject though.
BeforeDiscovery {
$script:testCases = Import-Csv $PSScriptRoot\TestCases.csv | ForEach-Object {
# Convert row (PSCustomObject) to hashtable.
$hashTable = #{}
$_.PSObject.Properties | ForEach-Object { $hashTable[ $_.Name ] = $_.Value }
# Implicit output that will be captured in array $script:testCases
$hashTable
}
}
Describe "Checking services" {
It "check <ServiceName> is running on <ComputerName>" -TestCases $script:testCases {
( get-service -computername $ComputerName -name $ServiceName ).status | Should -be "Running"
}
}
Note: While not strictly necessary I have put the code that reads the test cases into the BeforeDiscovery section, as suggested by the docs. This makes our intentions clear.

Azure DSC. HA Active Directory Domain Controller issue with Windows Server 2016

I'm trying to modify the official HA DC example to work with Windows Server 2016. https://github.com/Azure/azure-quickstart-templates/tree/master/active-directory-new-domain-ha-2-dc
After updating xActiveDirectory module that addresses race condition on Windows Server 2016 it gives me one more error. The final script that resides in ConfigureADBDC.ps1 fails:
Script script1
{
SetScript =
{
$dnsFwdRule = Get-DnsServerForwarder
if ($dnsFwdRule)
{
Remove-DnsServerForwarder -IPAddress $dnsFwdRule.IPAddress -Force
}
Write-Verbose -Verbose "Removing DNS forwarding rule"
}
GetScript = { #{} }
TestScript = { $false}
DependsOn = "[xADDomainController]BDC"
PowerShell DSC resource MSFT_ScriptResource failed to execute Set-TargetResource functionality with error message: Failed to get information for server ADBDC.
When I execute Get-DnsServerForwarder I see this:
PS C:\Users\adAdministrator> Get-DnsServerForwarder
UseRootHint : True
Timeout(s) : 3
EnableReordering : True
IPAddress :
ReorderedIPAddress :
However after some time it changes to this:
PS C:\Users\adAdministrator> Get-DnsServerForwarder
UseRootHint : True
Timeout(s) : 3
EnableReordering : True
IPAddress : 10.0.0.4
ReorderedIPAddress : 10.0.0.4
So, my question is. What is that DnsServerForwarder is used for? Is that even needed? How is it possible to fix this issue?
Well, a hackish way would be:
SetScript = {
do {
$dnsFwdRule = Get-DnsServerForwarder
} while ( $dnsFwdRule.IPAddress -eq $null )
if( $dnsFwdRule ) {
Remove-DnsServerForwarder -IPAddress $dnsFwdRule.IPAddress -Force
}
Write-Verbose -Verbose "Removing DNS forwarding rule"
}
note, this could lead to an infinite loop ;) you can fix that with adding something like this:
$i = 0
do
{
$i++
Start-Sleep 10
$dnsFwdRule = Get-DnsServerForwarder
}
while ($i -lt 10 -and $dnsFwdRule.IPAddress -eq $null)
as for the first question:
The Get-DnsServerForwarder cmdlet gets configuration settings on a DNS server. A forwarder is a Domain Name System (DNS) server on a network that is used to forward DNS queries for external DNS names to DNS servers outside that network.

Remote Powershell return value

I want to run a powershell with WinRM on serveral servers. I am using Invoke-Command with a script block.
I am reading from IIS and want to return a AppPool-Object, but I cannot access its properties - always empty.
#--imagine this code in a foreach block
$result = Invoke-Command -ComputerName $line.Servername -ScriptBlock {
Import-Module WebAdministration
$remotePoolName = Get-Item "IIS:\Sites\LeSite" #| Select-Object applictionPool
$pool = dir IIS:\AppPools | Where-Object { $_.Name -eq $remotePoolName.applicationPool }
return $pool
}
write-host $result.managedRuntimeVersion <- empty
Do I have to access it on the remote machine and return it as string ?
The problem here is, that you are referring to a property including get and set functions.
Using these functions outside of your server area results in nothing since the object is no longer in your server environment.
Using these functions inside your script block will work, because you use them on your server directly.
Greetz

Checking several Hyper-V Hosts for a specific VM in Powershell

I am writing a script to administer Hyper-VMs using the PowerShell Management Library for Hyper-V.
Since we are using several Hyper-V Hosts and our VMs can change their host for performance reasons or other reasons I need a script that finds out which Host a VM runs on for the following functions.
This was my try at accomplishing this:
function IdentifyHost
{
param
(
[parameter(Position=0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$VM
)
[Array]$hosts=Get-VMHost
if ($hosts.count -eq 0)
{
Write-Warning "No valid hosts found."
}
for ([int]$i=0; $i -lt $hosts.count; $i++ )
{
try
{
$out = Get-VM -Name $VM -Server $hosts[$i] -ErrorAction Stop
}
catch [UnauthorizedAccessException]
{
Write-Warning "Access to $hosts[$i] denied."
}
if ($VM -is [String])
{
if ($out.VMElementName -eq $VM )
{
return $out.__SERVER
}
}
elseif ($VM.ElementName -ne $null)
{
if ($out.VMElementName -eq $VM.VMElementName)
{
return $out.__SERVER
}
}
}
Write-Warning "No Host found for $VM"
}
Get-VMHost returns an array of all available Hyper-V hosts in the local area network.
My problem is that my function always returns the first element of the $hosts array whenever there is an UnauthorizedAccessException for the first element.
The plan is as following:
If the VM exists on the Host he will return a WMI Object representing that VM whose VMElementName property is equal to the VMs name given as parameter.
If the VM is given a WMI Object representing a VM the VMElementName properties of the two objects are equal.
If the VM does not exist on the Host he returns nothing.
If there's an access issue it should be catched.
But somehow it doesn't work out.
My question is this: What am I doing wrong in the code? And how can I fix it?
EDIT: The output of the function is the access problem warning for the first element of the $hosts array and then the first element of $hosts itself.
EDIT2: I fixed this myself by changing the return from the fragile $hosts[$i] to $out.__Server
Okay so I found a possible way of solving this issue:
Instead of returning the $hosts[$i] which yields unfavorable results I return the __Server property of $out, assuming there is a valid $out that matches the conditions.
If any of you guys knows a better or cleaner way of doing this, please by my guest.