I have a Powershell DSC script that installs an agent on a server and uses PSDscAllowPlainTextPassword = $true . Is there a way of parameterizing this DSC script so it can be run against multiple servers without needing to list all servers like below:
$Config = #{
AllNodes = #(
#{
NodeName = 'myServerOne'
PSDscAllowPlainTextPassword = $true
}
#{
NodeName = 'myServerTwo'
PSDscAllowPlainTextPassword = $true
}
)
}
Can I use something like this but also declare PSDscAllowPlainTextPassword?
Configuration InstallAgentDSC
{
param (
[Parameter(Mandatory=$true)]
[String[]]$ServerName
)
Node $ServerName
{}
}
InstallAgentDSC -ServerName $args[0]
Also, can the InstallAgentDSC -ServerName $args[0] run outside of the script?
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
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.
We've been researching desired state configuration, and I was asked to set up a prototype using powershell DSC to configure an IIS app pool. I know all the steps to creating a configuration, I just am unsure of what I might have in my configuration. I plan to use the xWebAdministration resource because it has things like xWebAppPool and xWedbAdministration. Are there any suggestions on what else I might use to set this up?
If you have many sites that you are trying to bring under configuration control, you could use my DSC generator to produce the DSC for IIS Features, Web sites, app pools, and virtual directories.
I then use Octopus Deploy to deliver the DSC to the server and apply the DSC.
https://github.com/kevinsea/dsc-generator
You would probably use the WindowsFeature resource to install the Roles and Features needed (Web-Server, etc.), you'd probably use the File resource to create the directory and maybe copy the site's files, you might use the Registry resource to enable the Web Management Service so that you can manage IIS remotely, then use the Service resource to start that service.
Some time ago I have been tasked with exactly the same challenge and I have created a prototype of DSC resource that would acomplish this. After initial tests this runs now in production.
Source code is on gihub (https://github.com/RafPe/cWebAdmin ) and all feedback would be more than welcome :)
Maybe this would give you an idea how to challenge this on your end
*Realize this is quite old, but just stumbled across it and wanted to offer recommendations for the scenario your DSCs get big/unwieldly.
I'd start off by separating your your node definition file from your configuration file then map a reference. I keep my files side-by-side, so I'd reference like below. I won't go into encryption, good MS guidance on that.
$File = 'WebApp\NodeDefinitions.psd1'
$Parent = Split-Path -Parent $PSScriptRoot
$Path = Join-Path $Parent $File
WebApp -RunAs $RunAs -ConfigurationData "$Path" -OutputPath $localpath -verbose
Now to make things a little more dynamic you'd create arrays or hash table collections in your node definition file like below for folders, sites, etc. *I did have to alter xcertificatedsc to have it pass an array of accounts, which isn't difficult if you need an assist.
#{
NodeName = "Vm-Web-1"
Role = "DevWeb","Web","WS","SMP"
IUSRS = "Domain\User1$","Domain\User2$"
Folders = #(
("Dir1","F:\inetpub\wwwroot\SomeApp","Present"),
("Dir2","F:\inetpub\wwwroot\SomeApp2","Present"),
("Dir3","F:\inetpub\wwwroot\SomeApp2","Absent") #If something's moved or a mistake
)
Sites = #(
#("MyApp1","Domain\User1$","Present"),
#("MyApp2","Domain\User2$","Present")
)
CertPerms= #{
"somecert#domain.com" = #("Domain\User1$","Domain\User2$")
}
},
Then I introduce non-node data in my configuration so that sites are more portable that we'll later join in the configuration ps1 file. I'll also show how I'd iterate folder creation.
#{
Name = "MyApp1"
PoolConfigName = "ApMyApp1"
PoolName = "ApMyApp1"
PoolRtVer = "v4.0"
SiteConfigName = "WaMyApp1"
SitePath = "ApMyApp1"
SiteName = "ApMyApp1"
SiteDepends = "[File]Dir1"
},
#{
Name = "MyApp2"
PoolConfigName = "ApMyApp2"
PoolName = "ApMyApp2"
PoolRtVer = "v4.0"
SiteConfigName = "WaMyApp2"
SitePath = "ApMyApp2"
SiteName = "ApMyApp2"
SiteDepends = "[File]Dir2"
},
To iterate over folders, you just reference elements of your array in the nodedefintion file
Foreach($Folder in $Node.Folders){
File $Folder[0]
{
Ensure = $Folder[2]
Type = "Directory"
DestinationPath = $Folder[1]
}
}
The site joining is a little more complex and I'm not overly happy with my current situation referencing the element instead of an easier to read name, but it works. After joining node/nonnode data, the next part of the script is for lesser used parameters. We don't introduce a lot of pools in always running mode for example (unless proper page initialization is validated in the web.config of the correlating app). If I'm introducing a site and the correlating service account isn't active on the domain, I'll make sure the correlating pool is stopped so it doesn't flood the iis worker process. Otherwise you should be able to map out what's set in the node section (array references) vs the non-node site data.
Foreach($SiteName in $Node.Sites){
$Site = $ConfigurationData.Sites.Where{$_.Name -eq $SiteName[0]}
if ([string]::IsNullOrWhiteSpace($Site.PoolIdleTO))
{
$PoolIdleTO = 20
}
else
{
$PoolIdleTO = $Site.PoolIdleTO
}
if ([string]::IsNullOrWhiteSpace($Site.PoolStartMode))
{
$PoolStartMode = "OnDemand"
}
else
{
$PoolStartMode = $Site.PoolStartMode
}
if ([string]::IsNullOrWhiteSpace($SiteName[3]))
{
$State = "Started"
}
else
{
$State = $SiteName[3]
}
xWebAppPool $Site.PoolConfigName
{
Name = $Site.PoolName
Ensure = $SiteName[2]
State = $State
autoStart = $true
enable32BitAppOnWin64 = $false
enableConfigurationOverride = $true
managedPipelineMode = "Integrated"
managedRuntimeVersion = $Site.PoolRtVer
startMode = $PoolStartMode
queueLength = 1000
cpuAction = "KillW3wp"
cpuLimit = 95000
cpuResetInterval = (New-TimeSpan -Minutes 1).ToString()
cpuSmpAffinitized = $false
cpuSmpProcessorAffinityMask = 4294967295
cpuSmpProcessorAffinityMask2 = 4294967295
identityType = 'SpecificUser'
Credential = New-Object System.Management.Automation.PSCredential($SiteName[1], (ConvertTo-SecureString $Node.GmsaPwd.ToString() -AsPlainText -Force))
idleTimeout = (New-TimeSpan -Minutes $PoolIdleTO).ToString()
idleTimeoutAction = 'Suspend'
loadUserProfile = $false
logEventOnProcessModel = 'IdleTimeout'
logonType = 'LogonBatch'
manualGroupMembership = $false
maxProcesses = 1
pingingEnabled = $true
pingInterval = (New-TimeSpan -Seconds 30).ToString()
pingResponseTime = (New-TimeSpan -Seconds 90).ToString()
setProfileEnvironment = $false
shutdownTimeLimit = (New-TimeSpan -Seconds 90).ToString()
startupTimeLimit = (New-TimeSpan -Seconds 90).ToString()
orphanActionExe = ''
orphanActionParams = ''
orphanWorkerProcess = $false
loadBalancerCapabilities = 'HttpLevel'
rapidFailProtection = $true
rapidFailProtectionInterval = (New-TimeSpan -Minutes 1).ToString()
rapidFailProtectionMaxCrashes = 5
autoShutdownExe = 'C:\Windows\System32\iisreset.exe'
autoShutdownParams = ''
disallowOverlappingRotation = $false
disallowRotationOnConfigChange = $false
logEventOnRecycle = 'Time,Requests,Schedule,Memory,IsapiUnhealthy,OnDemand,ConfigChange,PrivateMemory'
restartMemoryLimit = 3221225472
restartPrivateMemoryLimit = 8000000
restartRequestsLimit = 20000000
restartTimeLimit = (New-TimeSpan -Minutes 0).ToString()
restartSchedule = "00:00:00"
DependsOn = '[WindowsFeature]IIS'
}
<#!!!Imperative method (runs immediately) to ensure service accounts get IIS metabase access!!#
#Need to move this into the function with a flag, obviously there'll be looping challenges...
Invoke-Command -Session (New-PSSession -ComputerName $Node.NodeName -Credential $RunAs -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)) -ScriptBlock {
param ([string] $User)
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -ga $User
} -ArgumentList $SiteName[1]
#>
cIisAccess "IisMetabaseAccess$($Site.SiteConfigName + $SiteName[1])"
{
Account = $SiteName[1]
Ensure="Present"
}
xWebApplication $Site.SiteConfigName
{
Website = "Default Web Site"
Name = $Site.SiteName
WebAppPool = $Site.PoolName
PhysicalPath = $Node.DfSitePath + $Site.SitePath
Ensure = $SiteName[2]
PreloadEnabled = $true
DependsOn = "[xWebAppPool]$($Site.PoolConfigName)",$($Site.SiteDepends)
}
}
Where one thing to note is I use GMSAs so there's no password and a kerberos token is used. You'd still need a phony password reference due to PSCredential requirements, so you can just add something to your allnodes data like below for reference:
GmsaPwd = "none"
There's also lots of guidance on using roles out there, but a simple ref is below.
Node $AllNodes.Where{$_.Role -contains "Web"}.NodeName
{
#Embed site/folder iterators here if preferred
}
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.