This problem has occurred during learning, so I am not sure how likely it is in the real world, but I am still interested to know the best way to handle this sort of thing.
I have the following composite configuration:
configuration hDefaultServer {
Param
(
[Parameter(Mandatory)]
[string]$myFileName,
[Parameter()]
[ValidateSet("Present","Absent")]
[String]$Ensure = "Present"
)
WindowsFeature 'InstallGUI'
{
Ensure = $Ensure
Name = 'Server-Gui-Shell'
}
File 'Temp'
{
Type = 'Directory'
DestinationPath = "$($env:SystemDrive)\Temp"
Ensure = $Ensure
}
File 'SomeFile'
{
Type = 'File'
DestinationPath = "$($env:SystemDrive)\Temp\$myFileName"
Contents = 'This file was created by DSC!'
Ensure = $Ensure
}
}
And compile it into its .MOF with:
$ConfigData = #{
AllNodes = #(
#{ NodeName = "*"; PsDscAllowPlainTextPassword = $true },
#{ NodeName = 'WMF5-1' }
)
}
Configuration DemoDefaultServer
{
Param
(
[string]$FileName
)
Import-DscResource -Name hDefaultServer
hDefaultServer Demo2
{
myFileName = $FileName
Ensure = "Present"
}
}
DemoDefaultserver -ConfigurationData $ConfigData -OutputPath C:\Configurations\DemoDefaultServer -filename "SomeOtherFile.txt"
When I run it on a Windows 10 box, it completes the file and folder stuff, but errors as ServerManager doesn't exist on Windows 10 client:
PowerShell DSC resource MSFT_RoleResource failed to execute Test-TargetResource functionality with error message: Installing roles and features using PowerShell Desired State Configuration is supported only on Server
SKU's. It is not supported on Client SKU.
Which is fair enough. What is the best way to handle a case like this?
If I do something like this:
configuration hDefaultServer {
[cmdletbinding()]
Param
(
[Parameter(Mandatory)]
[string]$myFileName,
[Parameter()]
[ValidateSet("Present","Absent")]
[String]$Ensure = "Present"
)
$ProductType = (Get-WmiObject -Class Win32_OperatingSystem).ProductType
If($ProductType -eq 1)
{
Write-Verbose "Client OS ($ProductType)"
}
elseif($ProductType -eq 2)
{
Write-Verbose "Domain Controller ($ProductType)"
WindowsFeature 'InstallGUI'
{
Ensure = $Ensure
Name = 'Server-Gui-Shell'
}
}
else
{
Write-Verbose "Server OS ($ProductType)"
WindowsFeature 'InstallGUI'
{
Ensure = $Ensure
Name = 'Server-Gui-Shell'
}
}
File 'Temp'
{
Type = 'Directory'
DestinationPath = "$($env:SystemDrive)\Temp"
Ensure = $Ensure
}
File 'SomeFile'
{
Type = 'File'
DestinationPath = "$($env:SystemDrive)\Temp\$myFileName"
Contents = 'This file was created by DSC!'
Ensure = $Ensure
}
}
At compile time, the .MOF file is adjusted accordingly. i.e. the InstallGUI section is added or removed.
There is also DependsOn, but from reading, I believe this is to define the order of application within the scope of a configuration. I haven't worked out the pattern on how this works, but for instance, I wanted to make sure the folder was created before the file, I could do (and just to prove it, I've changed the order so the file is listed before the folder):
configuration hDefaultServer {
[cmdletbinding()]
Param
(
[Parameter(Mandatory)]
[string]$myFileName,
[Parameter()]
[ValidateSet("Present","Absent")]
[String]$Ensure = "Present"
)
$ProductType = (Get-WmiObject -Class Win32_OperatingSystem).ProductType
If($ProductType -eq 1)
{
Write-Verbose "Client OS ($ProductType)"
}
elseif($ProductType -eq 2)
{
Write-Verbose "Domain Controller ($ProductType)"
WindowsFeature 'InstallGUI'
{
Ensure = $Ensure
Name = 'Server-Gui-Shell'
}
}
else
{
Write-Verbose "Server OS ($ProductType)"
WindowsFeature 'InstallGUI'
{
Ensure = $Ensure
Name = 'Server-Gui-Shell'
}
}
File 'SomeFile'
{
Type = 'File'
DestinationPath = "$($env:SystemDrive)\Temp\$myFileName"
Contents = 'This file was created by DSC!'
Ensure = $Ensure
DependsOn = "[File]Temp"
}
File 'Temp'
{
Type = 'Directory'
DestinationPath = "$($env:SystemDrive)\Temp"
Ensure = $Ensure
}
}
Is there a way with Try / Catch? Similar to the if statement. I did try it, but the .MOF contained the InstallGUI section.
I believe there's probably a better way using ConfigurationData. Something like:
$configData{
AllNodes = #(
#{
NodeName = "WIN-AQEKG7L9SE8"
Role = "Setup, WindowsFeatures, IE, SqlServer"
}
(
}
But I haven't worked this bit out yet. All the examples I have found, using this, seem to use "Role", that I believe is part of ServerManager and therefore not available on Windows 10.
TIA.
I think it would be a good idea to create TWO different DSCs.
It makes no sense to do this type of check.
A DSC is used for mass configuration. Configure 10 web servers, configure 10 sql servers, etc.
Related
I am trying to seup SQL Server with PowerShell DSC with following configuration script.
After successful installation When I am trying to login with windows authentication it doesn't work and throws error "login failed for user <domain\user>" even though I am already part of administrators group.
This is my DSC configuration script
Am I missing anything?
Configuration InstallSQLServer
{
param(
[string[]]$NodeName = 'localhost'
)
Import-DscResource -ModuleName SqlServerDsc
Import-DscResource –ModuleName 'PSDesiredStateConfiguration'
node localhost
{
WindowsFeature 'NetFramework45'
{
Name = 'NET-Framework-45-Core'
Ensure = 'Present'
}
SqlSetup 'InstallDefaultInstance'
{
InstanceName = 'MSSQLSERVER'
Features = 'SQLENGINE'
SourcePath = 'C:\SQL2019'
SQLSysAdminAccounts = #("Administrators")
DependsOn = '[WindowsFeature]NetFramework45'
}
Registry REG_LoginMode{
DependsOn = '[SqlSetup]InstallDefaultInstance'
Key = 'HKEY_LOCAL_MACHINE\Software\Microsoft\MSSQLServer\MSSQLServer'
ValueName = 'LoginMode'
ValueType = 'DWORD'
ValueData = $Node.LoginMode
PsDscRunAsCredential = $SQLInstallCredential
}
SqlServerNetwork EnableTcpIp {
DependsOn = '[SqlSetup]InstallDefaultInstance'
InstanceName = 'MSSQLSERVER'
ProtocolName = 'Tcp'
IsEnabled = $true
TCPPort = 1433
RestartService = $true
}
}
}
#Create the MOF
InstallSQLServer -NodeName localhost
#Apply the Configuration
Start-DscConfiguration -Path .\InstallSQLServer -Wait -Force -Verbose
I am passing the userdata through terraform which changes the windows EC2 hostname. This userdata configuration is part of an autoscaling group launch configuration. For each windows EC2 instance which spins up in this Autoscaling group, I need to assign a different hostname. Like adding a number at the end of specified hostname - Eg. hostnamedev (hostnamedev01, hostnamedev02, ...). Thank you
init.ps1
#<<=======<<=======||Environment Variables from User Data||=========>>========>>
$newHostname = "${hostname}"
#<<===========================||Hostname Change||=============================>>
function UpdateHostname {
param (
[Parameter (Mandatory = $true)]
[string]$newHostname
)
If ($newHostname -ne $env:COMPUTERNAME) {
Try {
Rename-Computer -NewName $newHostname -Restart -ErrorAction Stop
}
Catch {
$ErrorMessage = $_.Exception.Message
Write-Output "Rename failed: $ErrorMessage"
}
}
else {
Write-Output "The Host Name is Updated already"
}
Write-Output "The Host Name is $env:COMPUTERNAME"
}
UpdateHostname -newhostname $newHostname
userdata.tf
data "template_file" "user_data" {
template = "${file("${path.module}/init.ps1")}"
vars = {
hostname = "hostnamedev"
}
}
module.tf
module my_module {
#...
user_data = data.template_file.user_data.rendered
}
ec2.tf
resource aws_launch_configuration launch_config {
image_id = var.ami_id
instance_type = var.instance_type
iam_instance_profile = var.iam_instance_profile_name
key_name = var.key_name
user_data = var.user_data
}
resource aws_autoscaling_group asg {
launch_configuration = aws_launch_configuration.launch_config.name
max_size = var.max_node
min_size = var.min_node
force_delete = true
lifecycle {
create_before_destroy = true
}
}
Is there a way to get a special folder from a remote machine?
I'm getting the local folder using this code:
$path = [environment]::getfolderpath("CommonApplicationData")
But I'd like to get it from other machine using a function like this:
$specialFolder = Get-RemoteSpecialFolder "MyRemoteMachine" "CommonApplicationData"
function Get-RemoteSpecialFolder
{
param(
[string]$ComputerName,
[string]$SpecialFolderName
)
Get-WMIObject ...
...
}
You can get this info by reading the remote computers registry (you need permissions of course) like with the function below:
function Get-RemoteSpecialFolder {
[CmdletBinding()]
param(
[string]$ComputerName = $env:COMPUTERNAME,
[string]$SpecialFolderName = 'Common AppData'
)
if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet) {
$regPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
try {
$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $ComputerName)
$regKey = $baseKey.OpenSubKey($regPath)
return $regkey.GetValue($SpecialFolderName)
}
catch {
throw
}
finally {
if ($regKey) { $regKey.Close() }
if ($baseKey) { $baseKey.Close() }
}
}
else {
Write-Warning "Computer '$ComputerName' is off-line or does not exist."
}
}
You should be able to find these common environment variables with this:
"Common Desktop"
"Common Start Menu"
"CommonVideo"
"CommonPictures"
"Common Programs"
"CommonMusic"
"Common Administrative Tools"
"Common Startup"
"Common Documents"
"OEM Links"
"Common Templates"
"Common AppData"
P.S. Put the function in first, then call on it
$specialFolder = Get-RemoteSpecialFolder "MyRemoteMachine" "Common AppData"
Is there any option to pass values to $configdata block (use for -ConfigurationData) as a parameter/variable?
Something like:
Configuration Config1
{
...
...
...
}
$configdata = #{
AllNodes = #(
#{
NodeName = servername
CertificateFile = "$path\CertFile.cer"
Thumbprint = $CertThumb
}
The Configuration Data is the way to pass values to the Configuration.
On the other hand, the configuration data itself is nothing more, but a Hash Table. You can edit it in any way you like.
Consider the following example.
You are in Push mode and have the following configuration:
Configuration MyFileCreator
{
Import-DscResource –ModuleName 'PSDesiredStateConfiguration'
Node localhost
{
File sampleFile
{
Ensure = $ConfigurationData.fileEnsure
Type = 'File'
DestinationPath = 'c:\temp\file.txt'
Force = $true
}
# Configure LCM
LocalConfigurationManager
{
ConfigurationMode = 'ApplyAndAutoCorrect'
RefreshMode = 'PUSH'
RebootNodeIfNeeded = $False
}
}
}
You see that I have used $ConfigurationData.fileEnsure. This variable refers to a value I set in the ConfigurationData.
My ConfigurationData could look like this:
$myConfigurationData = #{
AllNodes = #()
fileEnsure = 'absent'
}
If I want to apply my configuration with the configuration data, I can run the following commands:
MyFileCreator -ConfigurationData $myConfigurationData
Start-DscConfiguration -ComputerName localhost .\MyFileCreator
If I want to change the configuration data, I can simply modify the Hash Table and apply my configuration again:
$myConfigurationData.fileEnsure = 'present'
MyFileCreator -ConfigurationData $myConfigurationData
Start-DscConfiguration -ComputerName localhost .\MyFileCreator -Force
You can read more about the idea behind Configuration and Environment Data in the official MSDN Documentation.
I'm trying to use certificates to embed credentials into a Service resource. I've got PKI in the infrastructure and all my test servers are auto-enrolled. I exported their certs locally to work with and have them in my ConfigData as follows:
#{
AllNodes = #(
#{
NodeName = "*"
NeoConfigDestinationPath = "D:\ServerBox\Servers\JRun4\_build\shared\config"
}
#{
NodeName = 'DEVOPS'
Role = #('DSCPullServer')
CertificateFile = "D:\EQ_DSCModule\Certs\DEVOPS.cer"
Thumbprint = "AE4F10AE4141C8726EEEBE888C69FE7ABB3099A8"
}
#{
NodeName = 'Server1'
Role = #('IIS', 'ServerBox', 'DevInt')
CFServices = #("Adobe CF9 1", "Adobe CF9 2", "Adobe CF9 3", "Adobe CF9 4")
CertificateFile = "D:\EQ_DSCModule\Certs\Client1.cer"
Thumbprint = "4FA343A76AEA2B805850190E9C04AA9E2A82A162"
}
#{
NodeName = 'Server2'
Role = #('IIS', 'ServerBox', 'DevInt')
CFServices = #("Adobe CF9 1")
CertificateFile = "D:\EQ_DSCModule\Certs\Client2.cer"
Thumbprint = "0FCB76684F0C74495DEB54F637B50BDA7182483D"
}
)
ServerBoxConfig = #{
SourcePath = "\\Share\Path\DevOps\ServerBox"
DestinationPath = "D:\ServerBox"
}
DevIntConfig = #{
SourcePath = "\\Share\Path\DevOps\DevInt"
DestinationPath = "D:\ServerBox\IIS\wwwroot"
NeoConfigSourcePath = "\\Share\Path\DevOps\ServerConfig\Environments\DevInt\NeoConfig"
}
}
This this is the config script that I'm running:
$webCFDevCred = Get-Credential -Credential "svc-webcfdev#domain.com"
Configuration EqConfig
{
Import-DSCResource -Module xPSDesiredStateConfiguration
Import-DSCResource -Module cChoco
Node $AllNodes.NodeName {
cChocoInstaller installChoco {
InstallDir = "C:\ProgramData\Chocolatey"
}
}
Node $AllNodes.Where({ $_.role -eq 'DSCPullServer' }).NodeName { ... } #DSCPullServer
Node $AllNodes.Where({ $_.role -eq 'IIS' }).NodeName { ... } #IIS
Node $AllNodes.Where({ $_.role -eq 'ServerBox' }).NodeName {
File ServerBox
{
Ensure = "Present"
Type = "Directory"
Recurse = $true
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.ServerBoxConfig.SourcePath
DestinationPath = $ConfigurationData.ServerBoxConfig.DestinationPath
}
} #ServerBox
Node $AllNodes.Where({ $_.role -eq 'DevInt' }).NodeName {
File DevInt
{
Ensure = "Present"
Type = "Directory"
Recurse = $true
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.DevIntConfig.SourcePath
DestinationPath = $ConfigurationData.DevIntConfig.DestinationPath
DependsOn = "[File]ServerBox"
}
File DevInt_Config
{
Ensure = "Present"
Type = "Directory"
MatchSource = $true
Force = $true
Checksum = "modifiedDate"
SourcePath = $ConfigurationData.DevIntConfig.NeoConfigSourcePath
DestinationPath = $Node.NeoConfigDestinationPath
DependsOn = "[File]ServerBox"
}
#This runs a script to build out the ColdFusion cluster/servers
#Uses the number of services as the param for serverCount
cChocoPackageInstaller installServerBox {
Name = "ServerBox.DevInt -params $($Node.CFServices.Length)"
DependsOn = #("[cChocoInstaller]installChoco", "[File]DevInt_Config")
}
#Sets the services generated by the ServerBox script
$Node.CFServices.ForEach({
Service "Service-$_" {
Name = $_
State = 'Running'
Credential = $webCFDevCred
DependsOn = "[cChocoPackageInstaller]installServerBox"
}
})
} #DevInt
} #Configuration
EqConfig -ConfigurationData .\EQConfigData.psd1 -Output .\EqConfig -Verbose
Function Get-ComputerGuid
{
param (
[Parameter(Mandatory = $true)]
[string]$ComputerName
)
process
{
([guid]([adsisearcher]"(samaccountname=$ComputerName`$)").FindOne().Properties["objectguid"][0]).Guid
}
}
$DSCPullFolder = "C:\Program Files\WindowsPowerShell\DscService\Configuration"
Get-ChildItem .\EqConfig\* -Filter *.mof | ForEach-Object {
$guidMofFile = "$DSCPullFolder\$(Get-ComputerGuid $_.BaseName).mof"
$newMof = copy $_.FullName $guidMofFile -PassThru -Force
$newHash = (Get-FileHash $newMof).hash
[System.IO.File]::WriteAllText("$newMof.checksum", $newHash)
}
Configuration EqLocalConfig
{
Node $AllNodes.NodeName {
LocalConfigurationManager {
AllowModuleOverwrite = 'True'
CertificateID = $Node.Thumbprint
ConfigurationID = $(Get-ComputerGuid $nodeName)
ConfigurationModeFrequencyMins = 15
ConfigurationMode = 'ApplyAndAutoCorrect'
RebootNodeIfNeeded = 'True'
RefreshMode = 'PULL'
DownloadManagerName = 'WebDownloadManager'
DownloadManagerCustomData = (#{ ServerUrl = "https://DEVOPS:443/psdscpullserver.svc" })
}
}
}
EqLocalConfig -ConfigurationData .\EQConfigData.psd1 -Verbose
Set-DscLocalConfigurationManager -Path .\EqLocalConfig -Verbose
As far as I can tell it should work. My MOFs get generated with encrypted passwords inside, but when the client servers pick up the config and get to the Service step, it errors out. Checking the event viewer this is the details on the event:
"This event indicates that failure happens when LCM is processing the configuration. ErrorId is 0x1. ErrorDetail is The SendConfigurationApply function did not succeed.. ResourceId is [Service]Service-Adobe CF9 1 and SourceInfo is D:\EQ_DSCModule\EqConfig.ps1::285::4::Service. ErrorMessage is PowerShell provider MSFT_ServiceResource failed to execute Set-TargetResource functionality with error message: Failed to change 'Credential' property. Message: 'The 'Change' method of 'Win32_Service' failed with error code: '22'.' ."
According to MSDN (https://msdn.microsoft.com/en-us/library/aa384901%28v=vs.85%29.aspx) error code 22 on the Change method means "The account under which this service runs is either invalid or lacks the permissions to run the service." I know the service account works fine and I can add it myself using WMI as follows:
For ($i=0; $i -lt $clusterCount; $i++) {
(Get-WmiObject -Query "SELECT * FROM Win32_Service WHERE Name = 'Adobe CF9 $($i+1)'").Change($null,$null,$null,$null,$null,$null,'svc-webcfdev#domain.com','password',$null,$null,$null)
}
So if I can add the account using WMI, DSC should be able to as well, right? Ugh!
Ideas?
When you use both certificate files and thumbprint (certificateid) the encryption will happen with the certificate file but only the thumbprint (certificateid) you entered gets written to the .mof file. They can easily get out of sync. As a test try adding the certificates to the local machine store and then remove the reference to the certificate files from the script. Regenerate and fix any issues if a certificate can't be found. See if that fixes the issue.