-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathDSCManagement.psm1
782 lines (617 loc) · 26.6 KB
/
DSCManagement.psm1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
function Update-ConfigBlockData
{
param(
[string]$ConfigBlock,
[string]$Resource
)
$connection = Open-SqlConnection
$command = $connection.CreateCommand()
$command.commandtext = "UPDATE [dbo].[DSCResources]
SET [ConfigBlock] = '{0}'
WHERE [ResourceName] = '{1}';" -f $ConfigBlock,$Resource
$command.ExecuteNonQuery()
return
}
function Add-DscMultiValueColumn
{
param(
[string]$Resource,
[string]$Property,
[string]$Deliminator = ","
)
$connection = Open-SqlConnection
$query = "SELECT * FROM DSCResources WHERE ResourceName LIKE '$Resource'"
$dscresource = Initialize-Table -connection $connection -query $query
# Bit of a catch all, if the resource isn't in the DB or the property is invlaid it will fail.
if(!($dscresource.ConfigBlock -like "*$Property;*"))
{
Write-Output "Cannot locate $Property in ConfigBlock for Resource $Resource"
Write-Output "This is what was found:`n"
$dscresource.ConfigBlock
return
}
else
{
$newConfigBlock = $dscresource.ConfigBlock.Replace("$Property;","$Property -Split `"$Deliminator`";")
Update-ConfigBlockData -ConfigBlock $newConfigBlock -Resource $dscresource.ResourceName
}
}
# Creates the table required for DSC Resource metadata, probably not needed unless creating new DB.
function New-DBTableForDSCMetadata
{
[CmdletBinding()]
param (
[object]$connection
)
$command = $connection.CreateCommand()
try
{
$command.commandtext = "CREATE TABLE [dbo].[DSCResources](
[ResourceName] [varchar](max) NOT NULL,
[ResourceModule] [varchar](max) NOT NULL,
[ResourceModuleVersion] [varchar](max) NOT NULL,
[ResourceType] [varchar](max) NOT NULL,
[ConfigBlock] [varchar](max) NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]"
$command.ExecuteNonQuery()
}
catch
{
# The database already exists, at this point we will bail. I may add another switch parameter to update in case
# a resource is updated and another property is added.
Write-Host "$($_.Exception.Message)" -ForegroundColor White -BackgroundColor Red
return
}
}
# Function: New-DBTableFromResource
#
# New-DBTableFromResource can be used to quickly create tables based on the properties of the DSC Resource.
# Any further alterations can be made directly through SSMS.
function New-DBTableFromResource
{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$True)]
[string[]]$DscResName,
[object]$connection
)
# Taking from the pipeline
Begin
{
Write-Verbose "Adding Table(s)..."
}
Process
{
# Extract the properties from the resource for table columns
# TODO - This needs error handling, currently a bad resource name will lead to a table still being created.
[string]$PropBlock = ""
try {
if(($DscResObj = Get-DscResource $DscResName).Count -gt 1)
{
Write-Host "There appears to be more than one version of this resource present." -BackgroundColor Red
$DscResObj
$version = Read-Host "Please enter the version you wish to use: "
$DscResObj = $DscResObj | Where-Object {$_.Version -like $version}
}
}
catch
{
Write-Output "Problem locating resource named $DscResName"
return
}
$props = $DscResObj | Select-Object -ExpandProperty Properties
# Open a connection to SQL and create a 'System.Data.SqlClient.SqlCommand' object
if(!$connection)
{
$connection = Open-SqlConnection
}
$command = $connection.CreateCommand()
# Define a name for the new table based on resource if all succeeded above
$tablename = $DscResObj.Name + "Entries"
# Create the table with default columns of ID (primary key), partialset this will apply to and a identifier for MOF generation. This names are prefixed to avoid conflicts.
try
{
$command.commandtext = "CREATE TABLE [dbo].[$tablename](
[CoreID] [int] IDENTITY(1,1) NOT NULL,
[CorePlatform] [varchar](255) NOT NULL,
[CoreDescription] [varchar](255) NOT NULL,
PRIMARY KEY CLUSTERED
(
[CoreID] ASC
)WITH (PAD_INDEX = OFF, `
STATISTICS_NORECOMPUTE = OFF, `
IGNORE_DUP_KEY = OFF, `
ALLOW_ROW_LOCKS = ON, `
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]"
$command.ExecuteNonQuery() | Out-Null
Write-Host "Table $tablename created"
}
catch
{
# The database already exists, at this point we will bail. I may add another switch parameter to update in case
# a resource is updated and another property is added.
Write-Host "$($_.Exception.Message)" -ForegroundColor White -BackgroundColor Red
return
}
# Now add the columns, loop through the properties returned
foreach($prop in $props)
{
# We have to do a bit of string manipulation here as the '[' causes unexpected behaviour in
# string comparisons. Just strip them off and wild card to handle arrays.
# SQL contains some keywords which cannot be used for columns. Update the array below in case
# these are encountered. W
$SqlKeyWords = @('Key','Table','Index','Database')
# Check if $prop.Name is considered a keyword and change.
if($SqlKeyWords.Contains($prop.Name)){$prop.Name = $prop.Name+"Name"}
# Determine type and add to table definition
switch -wildcard ($($prop.PropertyType).TrimStart('[').TrimEnd(']'))
{
"string*"
{
if($prop.Name -notlike "DependsOn")
{
$command.commandtext = "ALTER TABLE $tablename `
ADD $($prop.Name) [varchar](max)"
$command.ExecuteNonQuery() | Out-Null
$PropBlock += $prop.Name + ' = ' + '$row.' + $prop.Name + ';'
}
}
"bool"
{
$command.commandtext = "ALTER TABLE $tablename `
ADD $($prop.Name) [bit]"
$command.ExecuteNonQuery() | Out-Null
$PropBlock += $prop.Name + ' = ' + '$row.' + $prop.Name + ';'
}
"UInt32*"
{
$command.commandtext = "ALTER TABLE $tablename `
ADD $($prop.Name) [int]"
$command.ExecuteNonQuery() | Out-Null
$PropBlock += $prop.Name + ' = ' + '$row.' + $prop.Name + ';'
}
}
}
# Start the ConfigBlock which will be saved to the DSCResource Table in order to build MOFs
$ConfigBlock = 'foreach($row in $' + $tablename + ') { ' + $DscResObj.Name + ' $row.CoreDescription {' + $PropBlock + '}}'
# Fixup the Config Block so any reserved SQL keyword is stored correctly as it's DSC resource property name.
foreach($word in $SqlKeyWords)
{
$ConfigBlock = $ConfigBlock.Replace($word + "Name =",$word + ' =')
}
# Temporary fix!!! - The file resource as of writing has no ModuleName or Version. Set these to default in box module
# This is ugly and needs to be handled better...
if($DscResObj.Name -eq 'File')
{
$command.commandtext = "INSERT INTO DSCResources (ResourceName,ResourceModule,ResourceModuleVersion,ResourceType,ConfigBlock) `
VALUES('{0}','{1}','{2}','{3}','{4}')" -f
$DscResObj.Name,'PSDesiredStateConfiguration','1.1',$DscResObj.ResourceType,$ConfigBlock
$command.ExecuteNonQuery() | Out-Null
Close-SQLConnection -connection $connection
return
}
# Update DSCResource metadata table
$command.commandtext = "INSERT INTO DSCResources (ResourceName,ResourceModule,ResourceModuleVersion,ResourceType,ConfigBlock) `
VALUES('{0}','{1}','{2}','{3}','{4}')" -f
$DscResObj.Name,$DscResObj.ModuleName,$DscResObj.Version.ToString(),$DscResObj.ResourceType,$ConfigBlock
# Send Command
$command.ExecuteNonQuery() | Out-Null
}
End
{
# Clean up connection
Close-SQLConnection -connection $connection
}
}
# Initialize-Table is a helper function that sends the SELECT query to the SQL server and populates a DataTable
function Initialize-Table
{
[CmdletBinding()]
param (
[object]$connection,
[string]$query
)
# Verbose output for test
Write-Verbose "Populating table from query '$query'"
# Create command from passed in connection, assign the query and execute.
$command = $connection.CreateCommand()
$command.CommandText = $query
$results = $command.ExecuteReader()
# Save the results to a table to return to the caller
$table = new-object "System.Data.DataTable"
$table.Load($results)
# Troubleshooting - this will be removed at some point but was intended to verify data being returned.
Write-Verbose "Row Count from table in Initialize-Table is $($table.Rows.Count)"
return $table
}
function Add-DscChangeRecord
{
[CmdletBinding()]
param (
[object]$connection,
[string]$user,
[string]$operation,
[string]$description,
[string]$date
)
$command = $connection.CreateCommand()
# T-SQL INSERT INTO string
$command.commandtext = "INSERT INTO ChangeLog (Name,Operation,Description,Date) `
VALUES('{0}','{1}','{2}','{3}')" -f
$user,$operation,$description,$date
# Send Command
$command.ExecuteNonQuery()
}
function Open-DSCSettings
{
[CmdletBinding()]
param (
[object]$connection,
[parameter(mandatory)]
[string]$Resource
)
$connection = Open-SqlConnection
# Target table based on user input - DB standard for table names is '<DscRescoureName>Entries'
$requiredTable = $Resource + "Entries"
# Create an array of available tables
$dbTables = Get-DscDBTables -connection $connection
# If a non-existent table has been specified then display what is available (drops the 'Entries' part of the table name)
if(!$dbtables.Name.ToUpper().Contains($requiredTable.ToUpper()))
{
Write-Host "`nUnknown resource, tables categories are available:`n"
$dbTables.Name.Replace('Entries','')
return
}
# DSC Settings - Uses a PowerShell Form Object
Add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$datagridview1 = New-Object 'System.Windows.Forms.DataGridView'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
$DGVhasChanged = $false
# Load the form and populate with selected table data
$form1_Load = {
$connection = Open-SqlConnection
$cmd = $connection.CreateCommand()
$cmd.CommandText = "SELECT * FROM $Resource" + "Entries"
$script:adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$dt = New-Object System.Data.DataTable
$script:adapter.Fill($dt)
$datagridview1.DataSource = $dt
$cmdBldr = New-Object System.Data.SqlClient.SqlCommandBuilder($adapter)
}
$buttonOK_Click = {
if ($script:DGVhasChanged -and [System.Windows.Forms.MessageBox]::Show('Do you wish to save?', 'Data Changed', 'YesNo')) {
$script:adapter.Update($datagridview1.DataSource)
}
}
$datagridview1_CurrentCellDirtyStateChanged = {
$script:DGVhasChanged = $true
}
$Form_StateCorrection_Load ={
$form1.WindowState = $InitialFormWindowState
}
$form1.SuspendLayout()
## Create the button panel to hold the OK and Cancel buttons
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size @(400,40)
$buttonPanel.Dock = "Bottom"
## Create the Cancel button, which will anchor to the bottom right
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 10
$cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Anchor = "Right"
## Create the OK button, which will anchor to the left of Cancel
$okButton = New-Object Windows.Forms.Button
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Top = $cancelButton.Top
$okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Anchor = "Right"
$okButton.add_Click($buttonOK_Click)
## Add the buttons to the button panel
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
# form1
$form1.Controls.Add($datagridview1)
$form1.Controls.Add($buttonPanel)
$form1.AcceptButton = $okButton
$form1.CancelButton = $cancelButton
$form1.ClientSize = '646, 374'
$form1.FormBorderStyle = 'Sizable'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = $ResourceType + "Entries Table Data"
$form1.add_Load($form1_Load)
$datagridview1.Anchor = 'Top, Bottom, Left, Right'
$datagridview1.ColumnHeadersHeightSizeMode = 'AutoSize'
$datagridview1.Location = '13, 13'
$datagridview1.Name = 'datagridview1'
$datagridview1.Size = '621, 309'
$datagridview1.TabIndex = 1
$datagridview1.add_CurrentCellDirtyStateChanged($datagridview1_CurrentCellDirtyStateChanged)
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '559, 339'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$buttonOK.UseVisualStyleBackColor = $True
$buttonOK.add_Click($buttonOK_Click)
$form1.ResumeLayout()
$InitialFormWindowState = $form1.WindowState
$form1.add_Load($Form_StateCorrection_Load)
# Display for the Form
$form1.ShowDialog()
}
# Help function to obtain a list of tables currently present in the database (Exported)
function Get-DscDBTables
{
[CmdletBinding()]
param (
[object]$connection
)
$connection = Open-SqlConnection
$query = "SELECT Name FROM Sys.Tables WHERE Name LIKE '%Entries'"
$table = Initialize-Table -connection $connection -query $query
return $table
}
# Get DSCSettings from the database
# TODO string comparisons are not great. Needs some variable work.
function Get-DscSettings
{
[CmdletBinding()]
param (
[string]$Resource,
[switch]$ListDBTables
)
$connection = Open-SqlConnection
# Target table based on user input - DB standard for table names is '<DscRescoureName>Entries'
$requiredTable = $Resource + "Entries"
# Create an array of available tables
$dbTables = Get-DscDBTables -connection $connection
if($PSBoundParameters.ContainsKey("ListDBTables"))
{
Write-Output "`nThe following DSC Resources have entries in the database:"
$dbTables
return
}
# If a non-existent table has been specified then display what is available (drops the 'Entries' part of the table name)
if(!$dbtables.Name.ToUpper().Contains($requiredTable.ToUpper()))
{
Write-Host "`nUnknown resource, tables categories are available:`n"
$dbTables.Name.Replace('Entries','')
return
}
# A valid table has been requested, assign the query value and send
$query = "SELECT * FROM $requiredTable"
$table = Initialize-Table -connection $connection -query $query
# Troubleshooting
Write-Verbose "Row Count from table in Get-DscSettings is $($table.Rows.Count)"
Close-SQLConnection $connection
return $table
}
function Open-SqlConnection
{
[CmdletBinding()]
param (
[string]$computername = ".\sqlexpress",
[string]$database = "DSC"
)
$connectionString = "Server=$computername;Database=$database;trusted_connection=true;"
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
Write-Verbose "Opening Connection $($connection.ConnectionString)"
$connection.Open()
return $connection
}
function Close-SqlConnection
{
[CmdletBinding()]
param (
[object]$connection
)
Write-Verbose "Closing Connection $($connection.ConnectionString)"
$connection.Close()
}
# Function: Update-ConfigBlock
#
# This will take a base configuration block and remove the redundant entries based on the columns
# used in the DataRow record.
#
# For Example, for the record below..
#
# Name : All
# Ensure :
# IncludeAllSubFeature : True
# LogPath : C:\Logs
# Source :
#
# We will change this ConfigBlock:
#
# {Name = $row.Name;Ensure = $row.Ensure;IncludeAllSubFeature = $row.IncludeAllSubFeature;LogPath = $row.LogPath;Source = $row.Source;}}
#
# To this:
#
# {Name = $row.Name;IncludeAllSubFeature = $row.IncludeAllSubFeature;LogPath = $row.LogPath;}}
#
# This ensures the unused columns of 'Ensure' and 'Source' are not referenced during COnfiguration script compile.
#
function Update-ConfigBlock
{
[CmdletBinding()]
param (
[string]$dbTable,
[string]$ConfigBlock,
[string]$Platform,
[string]$BasePlatform
)
# Open a connection to the DB to return records and run the query.
$connection = Open-SqlConnection
$query = "SELECT * FROM $dbTable WHERE CorePlatform LIKE '%$Platform%' OR CorePlatform LIKE '$BasePlatform'"
$TableEntries = @(Initialize-Table -connection $connection -query $query)
# No records found, as we've called this function to assign to a variable all output is returned
# See https://msdn.microsoft.com/powershell/reference/5.1/Microsoft.PowerShell.Core/about/about_Return
# for more details on PowerShell returns. For now I place a # in front so it ends up as a comment in the
# Config Script.
if($TableEntries.Count -eq 0)
{
Write-Output "# No records found for $dbTable with Platform of $Platform"
return
}
# We have data so extract the Table Columns so we can count the number and reference the names
[System.Data.DataTable] $table = $TableEntries[0].Table
# prefix name for the variables used in the script.
$tablePrefix = "newDSC$dbTable"
# Create a hash table with column names to bits, this will be checked with the bitmask to determine those column names in use
# Raising 2 to the power of i$ (starting at 0) will provide the Keys as bit values.
$columnNames = @{}
for($i = 0;$i -lt $table.Columns.Count;$i++)
{
$columnNames.Add([Math]::Pow(2, $i),$table.Columns[$i].ColumnName)
}
# Check each column in the DataRow and build a bitmask representing those in use.
foreach($row in $TableEntries)
{
$bitMaskValue = 0
for($i = 0;$i -lt $table.Columns.Count;$i++)
{
if(!$row.IsNull($i))
{
# Need to build bitmask of populated columns
$bitMaskValue = $bitMaskValue + [Math]::Pow(2, $i)
}
}
# Create a variable for each column 'in use' variation. Script scope used to clean up after but make available to caller.
# As we are looping through this we will create once then check for existing.
if(!(Test-Path Variable:\$($tablePrefix + $bitMaskValue)))
{
New-Variable -Name ($tablePrefix + $bitMaskValue) -Value @() -Scope 1
}
# Add the row to the correct array based on columns in use.
(Get-Variable -Name ($tablePrefix + $bitMaskValue)).Value += $row
}
# We should now have all the values placed into new tables based on the columns used. Output a new $configblock to an array
# that will then be written to the in memory configuration script
$configBlockArray = @()
foreach($newVariable in (Get-Variable -Name "newDSC$dbTable*"))
{
# We need to assign the $ConfigBlock passed in to a new varaible to avoid clashes.
$newBlock = $ConfigBlock
$newBlock = $newBlock.Replace($dbTable,"$($newVariable.Name)")
# Extract the bitmask from the end of the table name so we can use the value to evalate what needs to be removed from
# the configBlock string. The column names not in use will be added to
$bitmask = $newVariable.Name.Replace("$tablePrefix","")
$columnsToRemove = $columnNames.Keys | Where-Object {!($_ -band $bitmask)} | Foreach-Object {$columnNames.Get_Item($_)}
# Loop around the values to remove. This is quite straightforward as the text pattern is fixed when we wrote to DB.
$SqlKeyWords = @('Key','Table','Index','Database')
foreach($column in $columnsToRemove)
{
# We will need a special case here where a known keyword needs to be removed as we create the column as property + Name
# This string pattern is based on what is written into the DSCResources table
# Create a new variable to check is this name was previously from a SQL keyword
$keyWordCheck = $column.Replace('Name','')
if($SqlKeyWords.Contains($keyWordCheck))
{
$newBlock = $newBlock.Replace("$keyWordCheck = " + '$row' + ".$column;","")
}
else
{
$newBlock = $newBlock.Replace("$column = " + '$row' + ".$column;","")
}
}
# Add the new block to the array and loop back around if needed.
$configBlockArray += $newBlock
}
# Return the strings for inclusion in the configuration script.
return $configBlockArray
}
# Create a new MOF file from data stored in SQL, we build a configuration file in-memory and then run it.
function New-DscMOF
{
[CmdletBinding()]
param (
[string]$Platform,
[string]$ComputerName = "localhost",
[string]$ConfigName = "MySettings",
[string]$BasePlatform = "BaseOS",
[switch]$DebugConfig # Displays in memory config for troubleshooting
)
$connection = Open-SqlConnection
# DSC Resource metadata is stored in the DSCResources table. Read all of the settings from here so we can look
# to build configuration blocks
$query = "SELECT * FROM DSCResources"
$dscresources = Initialize-Table -connection $connection -query $query
Close-SQLConnection -connection $connection
# We will build an array of strings that will make up the script. This will then be executed once complete to produce the .MOF
$MyConf = @()
$MyConf += "Configuration $ConfigName {"
# Add modules used, this will not be reflected in final MOF if no settings for a particular resource are required. There
# are a lot of duplicates so we want to clean these up
$dscmodules = $dscresources | Select-Object ResourceModule,ResourceModuleVersion -Unique
foreach($row in $dscmodules)
{
$MyConf += "Import-DscResource -ModuleName $($row.ResourceModule)" + " -ModuleVersion $($row.ResourceModuleVersion)"
}
# Write the compunter name, defaults to localhost.
$MyConf += "node $ComputerName {"
# Add the ConfigBlock for each resource, this needs to review what columns are in use and build some globals for reference.
foreach($row in $dscresources)
{
$dbTableName = "$($row.ResourceName)Entries"
$MyConf += Update-ConfigBlock -dbTable $dbTableName -ConfigBlock $row.ConfigBlock -Platform $Platform -BasePlatform $BasePlatform
}
# Close the statements and add the call, there may be a need to add parameters here.
$MyConf += '}}'
$MyConf += "$ConfigName"
# Build the Config from the string array and add some line breaks.
$MyConfScript = ""
$MyConf | ForEach-Object {$MyConfScript += $_.ToString() + "`n"}
$MyConfScript = $ExecutionContext.InvokeCommand.NewScriptBlock($MyConfScript)
# Check whether any variables were created, if not then assume no records and dump the script.
if(!(Get-Variable -Name "newDSc*"))
{
Write-Host "No records found, please check the script below (possible platform typo?):`n"
Write-Host $MyConfScript
return
}
try
{
if($PSBoundParameters.ContainsKey('DebugConfig'))
{
Write-Output "Dumping Config Script for review...`n"
Write-Host $MyConfScript
}
& $MyConfScript -Verbose
}
catch [System.UnauthorizedAccessException]
{
Write-Host "$($_.Exception.Message)" -ForegroundColor White -BackgroundColor Red
Write-Host "Please ensure you are running with Administrator privileges" -ForegroundColor White -BackgroundColor Red
return
}
catch
{
Write-Host "$($_.Exception.Message)" -ForegroundColor White -BackgroundColor Red
return
}
}
Export-ModuleMember -Function Get-DscSettings,`
New-DscMOF,`
Initialize-Table,`
Open-SqlConnection,`
Close-SqlConnection, `
New-DBTableFromResource,`
Open-DSCSettings,`
New-DBTableForDSCMetadata,`
Get-DscDBTables, `
Add-DscMultiValueColumn