.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MachineConfiguration
2 |
3 | This solution descripes the steps how to implement an Automanage Machine Configuration (former GuestConfiguration) in order to Audit settings inside a machine and auto correct drifts.
4 | The advantage is in setting up only one policy for each Windows and Linux and provide a jobscript per assignment.
5 |
6 | The created policy will be assigned and reports the compliance state back to Azure Policy.
7 | The policy created can be assigned multiple times for different purposes, each assignment is unique as of a different parameter, the jobscript.
8 |
9 | **Example:**
10 | - Linux
11 | - Policy: LinuxGC
12 | - Assignment
13 | - Linux-InstallPackage1 (UX-InstallPackage01.ps1 jobscript)
14 | - Linux-StartService-xxx (UX-StartService-xxx.ps1 jobscript)
15 | - Linux-EnforceIPtables (UX-EnforceIPtables.ps1 jobscript)
16 | - Windows
17 | - Policy: WindowsGC
18 | - Assignment:
19 | - Windows-InstallPackage1 (Win-InstallPackage01.ps1 jobscript)
20 | - Windows-ApplyT0Hardening (Win-ApplyT0Hardening.ps1 jobscript)
21 | - Windows-AddRegKey-xxx (Win-AddRegKey-xxx.ps1 jobscript)
22 |
23 |
24 |
25 |
https://learn.microsoft.com/en-us/azure/governance/machine-configuration/
26 |
27 |
28 |
29 | **Possible use-cases**
30 | - Audit and/or Correct Files/Content/Registry
31 | - Audit and/or Correct Services/Deamons
32 | - Install Software
33 |
34 | **Target**
35 | - Windows and Linux machines
36 | - Azure and Arc connected machines
37 |
38 | **Report and Dashboard**
39 | - Result visible in Azure Policy
40 | - Policy evaluation via Azure ActivityLogs
41 | - Analyse result in Azure Resource Graph
42 | - recommendations visibile in Microsoft Defender for Cloud
43 |
44 | Workbook analysing the results
45 |
https://github.com/Azure/Microsoft-Defender-for-Cloud/tree/main/Workbooks/GuestConfiguration%20Result
46 |
47 | Workbook Azure Arc for Servers Monitoring
48 |
https://techcommunity.microsoft.com/t5/azure-arc-blog/azure-arc-for-servers-monitoring-workbook/ba-p/3298791
49 |
50 |
51 |
52 | **Setup and Verification**
53 | _Step 1 [Prepare Environment](./howto/Prepare.md)_
54 | _Step 2 [Create Policy](./howto/CreatePolicy.md)_
55 | _Step 3 [Create and Store Job Script](./howto/CreateJobScript.md)_
56 | _Step 4 [Policy Assignment](./howto/PolicyAssignment.md)_
57 | _Step 5 [Check Results](./howto/CheckResult.md)_
58 |
59 |
60 | ** **
61 | ## Solution Flowchart
62 | 
63 | 1. assigned policy downloads the GC policy packages (includes all to do the job)
64 | 2. machines extracts the package and execute DSC
65 | 3. the "Test" method is triggered
66 | 4. the jobscript is downloaded (green box) and stored on a local path, both provided by a parameter
67 | 5. the "Test Function" is triggered, returns the result
68 | 6. the desired state returns true or false, defines if the policy is Compliant or NonCompliant
69 | 7. the "Test" results decides if we trigger the "Set" before the "Get" method
70 | - a. the "Set" method is triggered
71 | - b the "Set Function" is triggered
72 | - c/d depending how to correct the drift, could be a binary download/setup or start a service
73 | 8. the "Get" method is triggered
74 | 9. the "Get Function" is triggered, returns the reason why the state is Compliant or NonCompliant
75 | 10. Result is reported back to Azure Policy
76 |
77 |
78 | Writing a custom DSC resource with PowerShell classes
79 |
https://learn.microsoft.com/en-us/powershell/dsc/resources/authoringResourceClass?view=dsc-1.1&viewFallbackFrom=dsc-3.0
80 |
--------------------------------------------------------------------------------
/howto/CheckResult.md:
--------------------------------------------------------------------------------
1 | **Steps**
2 | _Step 1 [Prepare Environment](./Prepare.md)_
3 | _Step 2 [Create Policy](./CreatePolicy.md)_
4 | _Step 3 [Create and Store Job Script](./CreateJobScript.md)_
5 | _Step 4 [Policy Assignment](./PolicyAssignment.md)_
6 | _--> Step 5 [Check Results]_
7 |
8 | ***
9 |
10 |
11 |
12 | ### Check the Result
13 | There are some ways to check the result
14 |
15 |
16 | Azure Policy -- CLICK ME
17 |
18 |
19 | Navigate to Azure Policy
20 |
21 | 
22 |
23 |
24 | ***
25 |
26 | 
27 |
28 |
29 | ***
30 |
31 | 
32 |
33 |
34 | ***
35 |
36 | 
37 |
38 |
39 | This screenshot show that the JobScript cannot be downloaded, in this case due to an expired token
40 |
41 | ***
42 |
43 | 
44 |
45 |
46 |
47 |
48 |
49 | Workbook -- CLICK ME
50 |
51 |
52 | I created a Azure Workbook visualizing Guest policies results and much more from data stored in ARG.
53 | Focus is on Azure and Arc connected machines
54 | _Download the code from MDC GitHub and import it in your Azure environment_
55 | _https://github.com/Azure/Microsoft-Defender-for-Cloud/tree/main/Workbooks/GuestConfiguration%20Result_
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 | Defender for Cloud - GUI -- CLICK ME
83 |
84 |
85 | 
86 |
87 | ***
88 |
89 | 
90 |
91 | ***
92 |
93 | 
94 |
95 | ***
96 |
97 | 
98 |
99 |
100 |
101 |
102 |
103 |
104 | Defender for Cloud Recommendation - Azure Resource Graph -- CLICK ME
105 |
106 |
107 | 
108 |
109 | ***
110 |
111 | 
112 |
113 |
114 |
115 |
116 | Defender for Cloud Recommendation - Log Analytics Continuous Export -- CLICK ME
117 |
118 |
119 | You can export recommendation including timestamp of a state change to a Log Analytics workspace or Event Hub.
120 | _Continuously export Microsoft Defender for Cloud data_
121 | _https://learn.microsoft.com/en-us/azure/defender-for-cloud/continuous-export_
122 |
123 | 
124 |
125 |
126 |
127 |
128 | Azure Resource Graph -- CLICK ME
129 |
130 |
131 | Navigate to Azure Resource Graph Explorer
132 |
133 | 
134 |
135 | ***
136 |
137 | 
138 |
139 |
140 |
141 |
142 |
143 | REST API -- CLICK ME
144 |
145 |
146 | _Azure Policy Guest Configuration REST API Reference_
147 | _https://learn.microsoft.com/en-us/rest/api/guestconfiguration/_
148 |
149 | _Microsoft Defender for Cloud_
150 | _https://learn.microsoft.com/en-us/rest/api/defenderforcloud/_
151 |
152 | 
153 |
154 | ***
155 |
156 | 
157 |
158 |
159 |
160 |
161 |
162 | PowerShell -- CLICK ME
163 |
164 |
165 | _PowerShell Az.Resources | Policy_
166 | _https://learn.microsoft.com/en-us/powershell/module/az.resources/?view=azps-9.4.0#policy_
167 |
168 | _PowerShell Az.PolicyInsights | Policy Insights_
169 | _https://learn.microsoft.com/en-us/powershell/module/az.policyinsights/?view=azps-9.4.0#policy-insights_
170 |
171 | _PowerShell Az.Security | Security_
172 | _https://learn.microsoft.com/en-us/powershell/module/az.security/?view=azps-9.4.0#security_
173 |
174 | 
175 |
176 | ***
177 |
178 | 
179 |
180 |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/howto/CreateJobScript.md:
--------------------------------------------------------------------------------
1 | **Steps**
2 | _Step 1 [Prepare Environment](./Prepare.md)_
3 | _Step 2 [Create Policy](./CreatePolicy.md)_
4 | _--> Step 3 [Create and Store Job Script]_
5 | _Step 4 [Policy Assignment](./PolicyAssignment.md)_
6 | _Step 5 [Check Results](./CheckResult.md)_
7 |
8 | ***
9 |
10 |
11 |
12 | ### Create and Store a job script
13 | As example, I created two simple jobscripts to audit and create a folder, one for Windows and Linux.
14 | Generally, there are three sections as descript in the design overview
15 |
16 | As second step, the scripts are stored in a Azure Storage account
17 |
18 |
19 | **JobScript**
20 | _function_TestScript: the result expects a empty result or $false to be false, any other result is true_
21 | _function_GetScript: the result expects a string_
22 | _function_SetScript: no result is expected, only provide the remediation logic_
23 |
24 |
25 | Windows
26 | ```powershell
27 | function function_TestScript
28 | {
29 | $result = Test-Path "c:/FolderSetupWIN01"
30 | return $result
31 | }
32 |
33 | function function_GetScript
34 | {
35 | $folder = "c:/FolderSetupWIN01"
36 | $foldercheck = Test-Path $folder
37 | $result = if (Test-Path $folder)
38 | {
39 | "Folder {0} is present" -f $folder
40 | }
41 | else {
42 | "Folder {0} is not present" -f $folder
43 | }
44 | return $result
45 | }
46 |
47 | function function_SetScript
48 | {
49 | new-item -ItemType Directory -path "c:/FolderSetupWIN01"
50 | }
51 | ```
52 |
53 |
54 | Linux
55 | ```powershell
56 | function function_TestScript
57 | {
58 | $result = Test-Path "/opt/FolderSetupUX01"
59 | return $result
60 | }
61 |
62 | function function_GetScript
63 | {
64 | $folder = "/opt/FolderSetupUX01"
65 | $foldercheck = Test-Path $folder
66 | $result = if (Test-Path $folder)
67 | {
68 | "Folder {0} is present" -f $folder
69 | }
70 | else {
71 | "Folder {0} is not present" -f $folder
72 | }
73 | return $result
74 | }
75 |
76 | function function_SetScript
77 | {
78 | new-item -ItemType Directory -path "/opt/FolderSetupUX01"
79 | }
80 | ```
81 |
82 |
83 | **Store the scripts and create a Blob SAS**
84 | The jobscripts are stored on a Blob container. Navigate to the Azure Portal (or alternatively use the upload function as we've done on the page before already). Create a container for scripts on the Storage account and upload the .ps1 files.
85 | Next, create a SAS token like in the following picture, copy the SAS Url
86 |
87 | 
88 |
89 |
90 | Copy the filename and the Blob SAS URL, this is needed for the assignment as parameter
91 |
92 |
93 |
94 |
95 | _Next: Step 4 [Policy Assignment](./PolicyAssignment.md)_
96 |
--------------------------------------------------------------------------------
/howto/CreatePolicy.md:
--------------------------------------------------------------------------------
1 | **Steps**
2 | _Step 1 [Prepare Environment](./Prepare.md)_
3 | _--> Step 2 [Create Policy]_
4 | _Step 3 [Create and Store Job Script](./CreateJobScript.md)_
5 | _Step 4 [Policy Assignment](./PolicyAssignment.md)_
6 | _Step 5 [Check Results](./CheckResult.md)_
7 |
8 | ***
9 |
10 |
11 |
12 | ### Create Configuration and Package
13 |
14 | Create a working folder for the first windows policy, Linux works exactly the same way by only changing a parameter (see below)
15 | e.g. GC4.3.0_Win
16 |
17 | Change folder dir in PowerShell
18 | ```powershell
19 | cd GC4.3.0_Win
20 | ```
21 |
22 |
23 | place the configuration file in working folder
24 | _GC4.3.0_Win\configuration.ps1_
25 |
26 | ```powershell
27 | Configuration CustomScript
28 | {
29 | param (
30 |
31 | )
32 |
33 | Import-DscResource -ModuleName ComPSScriptWinUX
34 | Node localhost
35 | {
36 | ComPSScriptWinUX PowershellScript
37 | {
38 | webpsscript = "empty"
39 | localscript = "empty"
40 | localpath = "empty"
41 | }
42 | }
43 | }
44 |
45 | CustomScript -OutputPath: ".\output"
46 | ```
47 |
48 |
49 |
50 |
51 | **execute configuration**
52 | ```powershell
53 | .\configuration.ps1
54 | ```
55 | A folder output is created with localhost.mof file
56 |
57 |
58 | **place the parameter file in working folder
**
59 | _GC4.3.0_Win\parameter.ps1_
60 | ```powershell
61 | # define parameters
62 | $PolicyParameterInfo = @(
63 | @{
64 | Name = 'localpath' # Policy parameter name (mandatory)
65 | DisplayName = 'Local path to PowerShell Script' # Policy parameter display name (mandatory)
66 | Description = 'Local path to PowerShell Script, e.g. $Env:SystemDrive\\_setup\\' # Policy parameter description (optional)
67 | ResourceType = 'ComPSScriptWinUX' # DSC configuration resource type (mandatory)
68 | ResourceId = 'PowershellScript' # DSC configuration resource id (mandatory)
69 | ResourcePropertyName = 'localpath' # DSC configuration resource property name (mandatory)
70 | ResourcePropertyValue = '$Env:SystemDrive\_setup\' # required for local testing
71 | DefaultValue = '$Env:SystemDrive\\_setup\\' # Policy parameter default value (optional)
72 | AllowedValues = @('$Env:SystemDrive\\_setup\\','/opt/_setup') # Policy parameter allowed values (optional)
73 | },
74 | @{
75 | Name = 'localscript' # Policy parameter name (mandatory)
76 | DisplayName = 'Local PowerShell Scriptname' # Policy parameter display name (mandatory)
77 | Description = 'Local name of PowerShell Script, e.g. custom.ps1' # Policy parameter description (optional)
78 | ResourceType = 'ComPSScriptWinUX' # DSC configuration resource type (mandatory)
79 | ResourceId = 'PowershellScript' # DSC configuration resource id (mandatory)
80 | ResourcePropertyName = 'localscript' # DSC configuration resource property name (mandatory)
81 | ResourcePropertyValue = 'FolderSetupWIN02.ps1'
82 | },
83 | @{
84 | Name = 'webpsscript' # Policy parameter name (mandatory)
85 | DisplayName = 'Web PowerShell Script' # Policy parameter display name (mandatory)
86 | Description = 'Path to PowerShell Script, e.g. https://customurl/script.ps1?sastoken' # Policy parameter description (optional)
87 | ResourceType = 'ComPSScriptWinUX' # DSC configuration resource type (mandatory)
88 | ResourceId = 'PowershellScript' # DSC configuration resource id (mandatory)
89 | ResourcePropertyName = 'webpsscript' # DSC configuration resource property name (mandatory)
90 | ResourcePropertyValue = 'https://airsazurepolicy.blob.core.windows.net/scripts-gc-v4-2/FolderSetupWIN02.ps1?sp=r&st=2023-01-23T15:36:07Z&se=2023-01-23T23:36:07Z&spr=https&sv=2021-06-08&sr=b&sig=5tvqnZSucFnBWWthQTPO9vTK2SBEEDiJDNrQ2fS6T%2FA%3D'
91 | }
92 | )
93 | ```
94 |
95 |
96 | Execute/load parameter (its dot space dot, verify the result by calling the variable $PolicyParameterInfo)
97 |
98 | ```powershell
99 | PS C:\Users\holger\Desktop\GC4.3.0_Win> . .\parameter.ps1
100 |
101 | PS C:\Users\holger\Desktop\GC4.3.0_Win> $PolicyParameterInfo
102 |
103 | Name Value
104 | ---- -----
105 | ResourceId PowershellScript
106 | Description Local path to PowerShell Script, e.g. $Env:SystemDrive\\_setup\\
107 | DefaultValue $Env:SystemDrive\\_setup\\
108 | AllowedValues {$Env:SystemDrive\\_setup\\, /opt/_setup}
109 | ResourceType ComPSScriptWinUX
110 | Name localpath
111 | ResourcePropertyValue $Env:SystemDrive\_setup\
112 | ResourcePropertyName localpath
113 | DisplayName Local path to PowerShell Script
114 | ResourceId PowershellScript
115 | Name localscript
116 | Description Local name of PowerShell Script, e.g. custom.ps1
117 | ResourceType ComPSScriptWinUX
118 | ResourcePropertyName localscript
119 | ResourcePropertyValue jobscript.ps1
120 | DisplayName Local PowerShell Scriptname
121 | ResourceId PowershellScript
122 | Name webpsscript
123 | Description Path to PowerShell Script, e.g. https://customurl/script.ps1?sastoken
124 | ResourceType ComPSScriptWinUX
125 | ResourcePropertyName webpsscript
126 | ResourcePropertyValue https://airsazurepolicy.blob.core.windows.net/blobcontainer/jobscript.ps1
127 | DisplayName Web PowerShell Script
128 |
129 | ```
130 |
131 |
132 | ** Define a name for the policy, e.g**
133 | ```powershell
134 | $policyname = "GC.4.3.0_WinCustomScript"
135 | ```
136 |
137 |
138 | **Create a GuestConfiguration Package**
139 |
140 | ```powershell
141 | New-GuestConfigurationPackage `
142 | -Force `
143 | -name $policyname `
144 | -Configuration .\output\localhost.mof `
145 | -Path .\ `
146 | -Verbose `
147 | -Type AuditAndSet
148 | ```
149 |
150 |
151 | output drops a zip packages
152 |
153 | ```powershell
154 | PS C:\Users\holger\Desktop\GC4.3.0_Win> New-GuestConfigurationPackage `
155 | -Force `
156 | -name $policyname `
157 | -Configuration .\output\localhost.mof `
158 | -Path .\ `
159 | -Verbose `
160 | -Type AuditAndSet
161 | VERBOSE: Starting New-GuestConfigurationPackage
162 | VERBOSE: Found resource dependency in mof with instance name '' and resource name 'ComPSScriptWinUX' from module 'ComPSScriptWinUX' with version '0.1.0'.
163 | VERBOSE: Found 1 resource dependencies in the mof.
164 | VERBOSE: Searching for a module with the name 'ComPSScriptWinUX' and version '0.1.0'...
165 | VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\ComPSScriptWinUX\ComPSScriptWinUX.psm1'.
166 | VERBOSE: Found the module dependencies: ComPSScriptWinUX
167 | VERBOSE: Creating the package root folder at the path 'C:\Program Files\WindowsPowerShell\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4.3.0_WinCustomScript'...
168 | VERBOSE: Creating the package Modules folder at the path 'C:\Program Files\WindowsPowerShell\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4.3.0_WinCustomScript\Modules'...
169 | VERBOSE: Setting the content of the package metaconfig at the path 'C:\Program Files\WindowsPowerShell\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4.3.0_WinCustomScript\GC.4.3
170 | .0_WinCustomScript.metaconfig.json'...
171 | VERBOSE: Copying the compiled DSC configuration (.mof) from the path 'C:\Users\holger\Desktop\GC4.3.0_Win\output\localhost.mof' to the package path 'C:\Program Files\WindowsPowerShe
172 | ll\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4.3.0_WinCustomScript\GC.4.3.0_WinCustomScript.mof'...
173 | VERBOSE: Copying module from 'C:\Program Files\WindowsPowerShell\Modules\ComPSScriptWinUX' to 'C:\Program Files\WindowsPowerShell\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4
174 | .3.0_WinCustomScript\Modules\ComPSScriptWinUX'
175 | VERBOSE: Compressing the generated package from the path 'C:\Program Files\WindowsPowerShell\Modules\GuestConfiguration\4.3.0\gcworker\temp\GC.4.3.0_WinCustomScript' to the package
176 | path 'C:\Users\holger\Desktop\GC4.3.0_Win\GC.4.3.0_WinCustomScript.zip'...
177 |
178 | Name Path
179 | ---- ----
180 | GC.4.3.0_WinCustomScript C:\Users\holger\Desktop\GC4.3.0_Win\GC.4.3.0_WinCustomScript.zip
181 | ```
182 |
183 |
184 | *Next steps are to upload the package and create/upload the policy. Both require a prior authentication against Azure in case we want to do it from the console. Without authentication from here you can upload the package to a https endpoint of your choice and upload the policy manually via the portal GUI*
185 |
186 |
187 | **Authentication**
188 | Verify whether the required module is installed, otherwise you get this error "The term 'connect-AzAccount' is not recognized as the name of a cmdlet"
189 |
190 | Check
191 | ```powershell
192 | Get-Module az.accounts
193 | ```
194 | Install
195 | ```powershell
196 | Install-Module az.accounts
197 | ```
198 | Connect
199 | ```powershell
200 | connect-AzAccount
201 | ```
202 |
203 |
204 | **Package Upload**
205 | Verify whether the required module is installed, otherwise you get this error "The command Get-AzStorageAccount is part of Azure PowerShell module "Az.Storage" and it is not installed."
206 |
207 | Check
208 | ```powershell
209 | Get-Module az.storage
210 | ```
211 | Install
212 | ```powershell
213 | Install-Module az.storage
214 | ```
215 |
216 |
217 | Load the function for uploading the just crated zip package to a blob storage account.
218 | Please adjust your Azure Subscription where your storage account is located (line 16) and SAS token lifetime (line 29)
219 |
220 | publish Function -- CLICK ME
221 |
222 |
223 | #### publish Function
224 |
225 | ```powershell
226 | function publish {
227 | param(
228 | [Parameter(Mandatory=$true)]
229 | $resourceGroup,
230 | [Parameter(Mandatory=$true)]
231 | $storageAccountName,
232 | [Parameter(Mandatory=$true)]
233 | $storageContainerName,
234 | [Parameter(Mandatory=$true)]
235 | $filePath,
236 | [Parameter(Mandatory=$true)]
237 | $blobName
238 | )
239 | # change to subscripton and back --> at the end
240 | $currentsub = (get-azcontext).subscription.id
241 | Select-AzSubscription f918b18e-2490-497c-97c8-34e84b191761 | out-null
242 | # Get Storage Context
243 | $Context = Get-AzStorageAccount -ResourceGroupName $resourceGroup `
244 | -Name $storageAccountName | `
245 | ForEach-Object { $_.Context }
246 | # Upload file
247 | $Blob = Set-AzStorageBlobContent -Context $Context `
248 | -Container $storageContainerName `
249 | -File $filePath `
250 | -Blob $blobName `
251 | -Force
252 | # Get url with SAS token
253 | $StartTime = (Get-Date)
254 | $ExpiryTime = $StartTime.AddYears('10') # TEN YEAR EXPIRATION
255 | $SAS = New-AzStorageBlobSASToken -Context $Context `
256 | -Container $storageContainerName `
257 | -Blob $blobName `
258 | -StartTime $StartTime `
259 | -ExpiryTime $ExpiryTime `
260 | -Permission rl `
261 | -FullUri
262 | # switch subscripton back to context that was before
263 | Select-AzSubscription $currentsub | Out-Null
264 | # Output
265 | return $SAS
266 | }
267 | ```
268 |
269 |
270 |
271 |
272 | Call the upload function.
273 | Please adjust the parameters prior that fits to your storage account
274 | ```powershell
275 | $uri = publish `
276 | -resourceGroup 'AIRS-Policy' `
277 | -storageAccountName 'airsazurepolicy' `
278 | -storageContainerName 'gcpolicies-v4-2' `
279 | -filePath (".\"+$policyname+".zip" ) `
280 | -blobName ($policyname+".zip")
281 | ```
282 | Verify the SAS token creation by calling the $uri variable
283 |
284 |
285 | **Generate Policy**
286 | The next step is the policy generation, change the Platform parameter to **"Linux"** if you create the Linux version. The result differs then when you look to the "if" statement of the created policy.
287 | Upload the policy to Azure Policy
288 |
289 |
290 | _Info:_
291 | _The Mode parameter, defines whether this is an Audit, Apply or AutoCorrect Policy._
292 | _Apply and AutoCorrect need a Remediation (on or after assignment) to be effective, if you don't trigger a remediation or disable the enforcement, the policy is Auditing only._
293 | _https://learn.microsoft.com/en-us/azure/governance/machine-configuration/machine-configuration-policy-effects#machine-configuration-assignment-types_
294 |
295 |
296 | ```powershell
297 | $policy = New-GuestConfigurationPolicy `
298 | -PolicyId (New-GUID) `
299 | -ContentUri $uri `
300 | -DisplayName $policyname `
301 | -Description 'Test policy with remediation option' `
302 | -Path '.\policyDefinitions' `
303 | -Platform 'Windows' `
304 | -PolicyVersion 1.0.0 `
305 | -Mode 'ApplyAndAutoCorrect' `
306 | -Parameter $PolicyParameterInfo `
307 | -Verbose
308 | ```
309 |
310 | Verifiy the policy has been created
311 | _policyDefinitions\GC.4.3.0_WinCustomScript_DeployIfNotExists.json_
312 |
313 |
314 | **Upload the policy to Azure Policy**
315 | Verify whether the required module is installed, otherwise you get this error "The command New-AzPolicyDefinition is part of Azure PowerShell module "Az.Resources" and it is not installed."
316 |
317 | Check
318 | ```powershell
319 | Get-Module az.resources
320 | ```
321 | Install
322 | ```powershell
323 | Install-Module az.resources
324 | ```
325 |
326 |
327 | The policy must be stored on either a MGMT group or a subscription.
328 | _Consider, the assignment can only be done on the same or a deeper level!_
329 |
330 |
331 |
332 | Subscription:
333 | Policy will be stored to the current context
334 | Verify: Get-AzContext
335 | Change: Set-AzContext "subscription"
336 | Upload:
337 | ```powershell
338 | New-AzPolicyDefinition -Name $policy.name -Policy $policy.path
339 | ```
340 |
341 |
342 | MGMT Group:
343 | Define the name of the MGMT Group, if you want to store the policy on the Tenant Root Group, provide the Tenant ID
344 | Upload:
345 | ```powershell
346 | New-AzPolicyDefinition -Name $policy.name -Policy $policy.path -ManagementGroupName myTenantID
347 | ```
348 |
349 |
350 | Verify policy upload
351 | 
352 |
353 |
354 |
355 | _Next: Step 3 [Create and Store Job Script](./CreateJobScript.md)_
--------------------------------------------------------------------------------
/howto/PolicyAssignment.md:
--------------------------------------------------------------------------------
1 | **Steps**
2 | _Step 1 [Prepare Environment](./Prepare.md)_
3 | _Step 2 [Create Policy](./CreatePolicy.md)_
4 | _Step 3 [Create and Store Job Script](./CreateJobScript.md)_
5 | _--> Step 4 [Policy Assignment]_
6 | _Step 5 [Check Results](./CheckResult.md)_
7 |
8 | ***
9 |
10 |
11 |
12 | ### Azure VM Prerequesites
13 | as mentioned on the _[Prepare Environment](./Prepare.md)_ site, we need to install the AzurePolicy extension with a managed identity on Azure VMs. You can ignore both settings for Arc machines. Either deploy it manually on each VM, or assign the following built-in policy initiative that is present in the system already.
14 |
15 |
16 | **_Deploy prerequisites to enable Guest Configuration policies on virtual machines_**
17 |
18 |
19 |
20 | Verify the extension and managed identity is installed. If the policy is assigned to existing VMs, a remediation is required to deploy the resources. Newly created VMs should get the settings applied by the policy within 15 minutes.
21 |
22 |
23 |
24 | ### Policy Assignment
25 | The policy definitions can be assigned directly, but I want the results also visible as Recommendation in _Microsoft Defender for Cloud_. Therefore we need to create an initiative and assign these.
26 |
27 |
28 | **Create Initiative**
29 | 
30 |
31 |
32 | ***
33 |
34 | 
35 |
36 |
37 | ***
38 |
39 | 
40 |
41 |
42 | ***
43 |
44 | 
45 |
46 |
47 | ***
48 |
49 | 
50 |
51 |
52 | Select Group and Click Save
53 |
54 | ***
55 |
56 | Set the parameters
57 | - Include Arc machines in case you want treat them like Azure VMs
58 | - Select the Local path for the jobscript, Default is the Windows version, for Linux adjust it
59 | - Local PowerShell Script Name --> put in the ScriptName.ps1
60 | - Web PowerShell Script --> put in the SAS URI
61 |
62 | 
63 |
64 |
65 | ***
66 |
67 | 
68 |
69 |
70 | **Assign the Initiative**
71 | if you don't want the results in Microsoft Defender for Cloud, do the assignment in Azure Policy.
72 | go to _Microsoft Defender for Cloud_
73 |
74 | 
75 |
76 |
77 | Select the Subscription of MGMT Group where the assignment should be done
78 |
79 | ***
80 |
81 | 
82 |
83 |
84 | ***
85 |
86 | 
87 |
88 |
89 | ***
90 |
91 | 
92 |
93 |
94 | ***
95 |
96 | 
97 |
98 |
99 | Change the Identity location if required
100 | Click Review + create and Create
101 |
102 |
103 | This needs now some time for the machines to evaluate the policy.
104 | Currently we have not triggered a remediation, we need the result first. At this moment we're in Audit only mode.
105 | The assignment gives the possibility to remediate but only a single policy.
106 | New VMs, created after the initiative assignment was done, are remediated automatically.
107 |
108 |
109 | **Trigger a Remediation Task**
110 | Once the policy has evaluated and finds noncompliant results we can start the remediation
111 |
112 | 
113 |
114 |
115 | ***
116 |
117 | 
118 |
119 |
120 | ***
121 |
122 |
123 | **Congratulations, you have done the whole job :relaxed:**
124 | You have deployed custom Machine Configuration policies and assigned it with a jobscript.
125 | Now you are ready to extend the solution by simply upload additional jobscripts and assign it with the existing policies.
126 |
127 | The evaluation and report creation need some time.
128 | Relax, take your time, fill up your coffee :coffee:
129 | Don't forget to follow the last link and check your results
130 |
131 |
132 | _Step 5 [Check Results](./CheckResult.md)_
133 |
--------------------------------------------------------------------------------
/howto/Prepare.md:
--------------------------------------------------------------------------------
1 | **Steps**
2 | _--> Step 1 [Prepare Environment]_
3 | _Step 2 [Create Policy](./CreatePolicy.md)_
4 | _Step 3 [Create and Store Job Script](./CreateJobScript.md)_
5 | _Step 4 [Policy Assignment](./PolicyAssignment.md)_
6 | _Step 5 [Check Results](./CheckResult.md)_
7 |
8 | ***
9 |
10 |
11 |
12 | ### What do we need:
13 | - a Windows Dev machine
14 | - a storage account
15 | - Azure Environment with Arc and/or Azure VMs to verify results
16 |
17 |
18 |
19 | ### Prepare Environment
20 | First an environment is required, where we built the policy packages for both, Windows and Linux. For the building process we use a Windows Server, by now I did setups with Server 2016, 2019 and 2022 -- all are working.
21 | For the basic setup, as descript on the following pages, no more machines are required (Only that one where the policies are finally assigned to verify the functionality).
22 |
23 | The generated policy .zip package brings all content and code required to execute the jobscript. The sources (PowerShell and DSC) and logic (how to get and handle the jobscript) is included in the policy package. The jobscript's download location and the location where to store it on the local machine are provided as parameters when the policy is assigned or added to an policy initiative. The jobscript provides the logic and steps how to evaluate a desired state and correct a drift on the target machine. PowerShell code is used on Windows and Linux machines as well.
24 |
25 |
26 |
27 |
28 | Nevertheless, later on when scripts become more complex, it makes more sense to develop and test jobscripts live on the target systems than wait for Azure Policy to evaluate states and reports it to the backend.
29 |
30 |
31 | In this case install PowerShell Core on the respective system.
32 | Windows:
33 | https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3
34 | Linux:
35 | https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux?view=powershell-7.2
36 |
37 |
38 |
39 |
40 | The second part is a location, where the content is stored. This means the policy package, the jobscript and if needed software binaries. Any location is possible, as long as the name resolution works and the client gets a valid certificate on accessing an _https://_ endpoint.
41 | In this example I use Azure Blob storage with SAS tokens. The token will be generated later on, when data is uploaded. For now we need a Storage account with at least one containers.
42 |
43 | Create a storage account
44 | https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal
45 |
46 | Quickstart: Upload, download, and list blobs with the Azure portal
47 | https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal
48 |
49 | Grant limited access to Azure Storage resources using shared access signatures (SAS)
50 | https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview
51 |
52 |
53 |
54 |
55 |
56 | In order machines in Azure are able to recognize assigned Machine Configuration policies and upload a report to Policy backend, they need a small agent. That one is deployed as an VM extension (AzurePolicyforWindows / AzurePolicyforLinux). Additionally, machines need a managed identity. Later, when we assign the policy, we will also do the assignment of built-in policies doing the work on the machines without user interaction. Azure creates the managed identity for Arc machines automatically and does not require the extension, as these is part of the Arc (connected machine) agent.
57 |
58 | Deploy requirements for Azure virtual machines
59 | https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview#deploy-requirements-for-azure-virtual-machines
60 |
61 |
62 | Multiple assignments with different parameters are supported now, therefore we can use same policies not only once. For Windows and Linux different policies are built. Technically they "could" use the same definition, the difference here are the "if" statements inside the policy only. The statement acts as filter according to which configuration attribute a resource is considered and which are out of scope.
63 |
64 | The policy definition is finally created by the GuestConfiguration CmdLet, see below. Depending on the version of the CmdLet and their release dates, different Windows images, Linux distributions, etc. are included in the "if" statement. This can be modified to include official unsupported systems after the definition creation, or when it has been stored in Azure already.
65 |
66 |
67 | Multiple assignments
68 |
69 | https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview#multiple-assignments
70 |
71 |
72 |
73 | Supported Client types:
74 |
75 | https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview#supported-client-types
76 |
77 |
78 |
79 |
80 |
81 | ### Setup on Dev machine
82 |
83 | Setup your authoring environment
84 |
[How to install the machine configuration authoring module | Microsoft Learn](https://learn.microsoft.com/en-us/azure/governance/machine-configuration/machine-configuration-create-setup)
85 |
86 | Install PowerShell on Windows
87 |
https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3
88 |
89 | ```powershell
90 | # Install Module
91 | Install-Module -Name GuestConfiguration
92 | ```
93 |
94 | 
95 |
96 |
97 |
98 | I will describe the upload of the policy and package with PowerShell. Therefore, the following CmdLets are required on the Dev machine. You can skip it in case a manual upload is preferred.
99 |
100 | ```powershell
101 | # Install Module
102 | Install-Module -Name az.accounts # authentication against Azure
103 | Install-Module -Name az.storage # upload policy package
104 | Install-Module -Name az.resources # upload policy
105 | ```
106 |
107 |
108 |
109 |
110 |
111 | ### Install DSC module on Dev machine
112 |
113 | Place the DSC module in a PowerShell module folder
114 | e.g. "C:\Program Files\WindowsPowerShell\Modules
115 |
116 | _C:\Program Files\WindowsPowerShell\Modules\ComPSScriptWinUX_
117 | - _ComPSScriptWinUX.psd1_
118 | - _ComPSScriptWinUX.psm1_
119 |
120 | ComPSScriptWinUX.psd1 -- CLICK ME
121 |
122 |
123 | #### ComPSScriptWinUX.psd1
124 |
125 | ```powershell
126 | #
127 | # Module manifest for module 'ComPSScript'
128 | #
129 | # Generated by: Holger Wache
130 | #
131 | # Generated on: 02/03/2022
132 | #
133 |
134 | @{
135 |
136 | # Script module or binary module file associated with this manifest.
137 | RootModule = 'ComPSScriptWinUX.psm1'
138 |
139 | # Version number of this module.
140 | ModuleVersion = '0.1.0'
141 |
142 | # ID used to uniquely identify this module
143 | GUID = '2e9859fa-1d2c-484a-bcd7-de9bc4f118d0'
144 |
145 | # Author of this module
146 | Author = 'Holger Wache'
147 |
148 | # Company or vendor of this module
149 | CompanyName = 'Holger Wache'
150 |
151 | # Copyright statement for this module
152 | Copyright = '(c) Holger Wache. All rights reserved.'
153 |
154 | # Description of the functionality provided by this module
155 | Description = 'Trigger external Script for use in Guest Configuration, Windows and Linux supported'
156 |
157 | # Minimum version of the Windows PowerShell engine required by this module
158 | PowerShellVersion = '5.1'
159 |
160 | # Functions to export from this module
161 | FunctionsToExport = @()
162 |
163 | # DSC resources to export from this module
164 | DscResourcesToExport = @()
165 |
166 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
167 | PrivateData = @{
168 |
169 | PSData = @{
170 |
171 | # Tags applied to this module. These help with module discovery in online galleries.
172 | # Tags = @(Power Plan, Energy, Battery)
173 |
174 | # A URL to the license for this module.
175 | # LicenseUri = ''
176 |
177 | # A URL to the main website for this project.
178 | # ProjectUri = ''
179 |
180 | # A URL to an icon representing this module.
181 | # IconUri = ''
182 |
183 | # ReleaseNotes of this module
184 | # ReleaseNotes = ''
185 |
186 | } # End of PSData hashtable
187 |
188 | }
189 | }
190 | ```
191 |
192 |
193 |
194 | ComPSScriptWinUX.psm1 -- CLICK ME
195 |
196 |
197 | #### ComPSScriptWinUX.psm1
198 |
199 | ```powershell
200 | class Reason {
201 | [DscProperty()]
202 | [string] $Code
203 |
204 | [DscProperty()]
205 | [string] $Phrase
206 | }
207 |
208 |
209 | function Get-ScriptPath
210 | {
211 | param(
212 | [parameter(Mandatory = $true)]
213 | [ValidateNotNullOrEmpty()]
214 | [String]$localpath,
215 |
216 |
217 | [parameter(Mandatory = $true)]
218 | [ValidateNotNullOrEmpty()]
219 | [String]$localscript
220 | )
221 |
222 |
223 | # correct problem where Drive "$env" is not found!
224 | Write-Verbose -Message "localpath is [$localpath]"
225 | Write-Verbose -Message "localscript is [$localscript]"
226 |
227 | if ($localpath -like "*Env:SystemDrive*")
228 | {
229 | $sysdrive = $Env:SystemDrive
230 | Write-Verbose -Message "systemdrive is [$sysdrive]"
231 |
232 | $localpath = $localpath.Replace($localpath.split("\")[0],$sysdrive)
233 | Write-Verbose -Message "changed localpath to [$localpath]"
234 | }
235 |
236 | # define file location
237 | $psscript = $localpath + $localscript
238 | Write-Verbose -Message "scriptpath is [$psscript]"
239 |
240 | $return = @{
241 | scriptpath = $psscript
242 | scrptname = $localscript
243 | scriptdir = $localpath
244 | }
245 |
246 | return $return
247 | }
248 |
249 | [DscResource()]
250 | class ComPSScriptWinUX {
251 |
252 | [DscProperty(Key)]
253 | [string]
254 | $webpsscript
255 |
256 | [DscProperty(Mandatory)]
257 | [string]
258 | $localscript
259 |
260 | [DscProperty(Mandatory)]
261 | [string]
262 | $localpath
263 |
264 | [DscProperty(NotConfigurable)]
265 | [Reason[]]
266 | $Reasons
267 |
268 |
269 |
270 | [ComPSScriptWinUX] Get() {
271 |
272 | $this.reasons = @()
273 | $reason = $null;
274 |
275 | # call function to get scriptpath
276 | $psscript = Get-ScriptPath -localpath $this.localpath -localscript $this.localscript
277 |
278 |
279 | # test script is present
280 | if (Test-Path $psscript.scriptpath)
281 | {
282 | write-verbose -Message "original downloaded script from [$this.webpsscript]"
283 | write-verbose -Message "script is correctly stored in [$psscript.scriptpath]"
284 | } else {
285 | write-verbose -Message "Error script not stored in [$psscript.scriptpath]"
286 | }
287 |
288 | # execute script to load functions
289 | $err = $null
290 | try
291 | {
292 | .$psscript.scriptpath -ErrorAction SilentlyContinue
293 | $script = function_GetScript -ErrorAction SilentlyContinue
294 | write-verbose -Message "Getresult = [$script]"
295 | } catch {
296 | $err = "Error loading script or Get Function in {0}" -f $psscript.scriptpath
297 | }
298 |
299 | Write-Verbose -Message "Script to Get is [$psscript.scriptpath]"
300 | if ($err) {Write-Verbose -Message "[$err]"}
301 |
302 | #Write-Verbose "Use this cmdlet to deliver information about command processing."
303 | #Write-Debug "Use this cmdlet to write debug information while troubleshooting."
304 |
305 |
306 | $phrase = $null
307 | if ($script) {$phrase = "GetResult: {0}" -f $script}
308 | else {[string]$phrase = "Error: {0}" -f $err}
309 |
310 | $code = "local: {0} | web: {1}" -f $psscript.scriptpath, $this.webpsscript
311 |
312 | # build reason table
313 | write-verbose -Message "Reason-Code = [$code]"
314 | write-verbose -Message "Reason-Phrase = [$phrase]"
315 | $reason = @{
316 | Code = $code
317 | Phrase = $phrase
318 | }
319 |
320 | $this.reasons += $reason
321 | return @{
322 | reasons = $this.reasons
323 | }
324 |
325 |
326 | }
327 |
328 |
329 | [void] Set() {
330 |
331 | $err = $null
332 |
333 | # call function to get scriptpath
334 | $psscript = Get-ScriptPath -localpath $this.localpath -localscript $this.localscript
335 |
336 |
337 |
338 | # execute script and load functions
339 | try
340 | {
341 | .$psscript.scriptpath -ErrorAction SilentlyContinue
342 | Write-Verbose -Message "Script to Set is [$psscript.scriptpath]"
343 |
344 | function_SetScript -ErrorAction SilentlyContinue -wait
345 | write-verbose -Message "Triggered Set function in [$psscript.scriptpath]"
346 | } catch {
347 | $err = "Error loading script or Set Function in {0}" -f $psscript.scriptpath
348 | }
349 |
350 | if ($err) {Write-Verbose -Message "[$err]"}
351 | }
352 |
353 |
354 | [bool] Test() {
355 |
356 |
357 | $err = $null
358 |
359 | # call function to get scriptpath
360 | $psscript = Get-ScriptPath -localpath $this.localpath -localscript $this.localscript
361 |
362 | Write-Verbose -Message "scriptpath is [$psscript.scriptpath]"
363 |
364 |
365 | # download and refresh script from web on every run
366 | if (($this.webpsscript -like "https://*") -and ($this.webpsscript -like "*.ps1*"))
367 | {
368 | # remove old content
369 | if (Test-Path $psscript.scriptpath)
370 | {
371 | Remove-Item $psscript.scriptpath
372 | }
373 |
374 | # create local dir
375 | if (!(test-path $psscript.scriptdir))
376 | {
377 | # [System.IO.Directory]::CreateDirectory($directory) | Out-Null
378 | New-Item -ItemType Directory -Path $psscript.scriptdir | Out-Null
379 | }
380 |
381 | # store content
382 | Invoke-WebRequest -Uri $this.webpsscript -UseBasicParsing -OutFile $psscript.scriptpath
383 | # test job
384 | if (Test-Path $psscript.scriptpath)
385 | {
386 | write-verbose -Message "Downloaded script from [$this.webpsscript]"
387 | write-verbose -Message "stored script to [$psscript.scriptpath]"
388 | } else {
389 | write-verbose -Message "Error storing webscript to [$psscript.scriptpath]"
390 | }
391 | }
392 |
393 |
394 | # execute script and load functions
395 | try
396 | {
397 | .$psscript.scriptpath -ErrorAction SilentlyContinue
398 | Write-Verbose -Message "Script to Test is [$psscript.scriptpath]"
399 |
400 | $script = function_TestScript -ErrorAction SilentlyContinue -wait
401 | write-verbose -Message "Testresult = [$script]"
402 | } catch {
403 | $err = "Error loading script or Test Function in {0}" -f $psscript.scriptpath
404 | }
405 |
406 | if ($err) {Write-Verbose -Message "[$err]"}
407 |
408 |
409 | #Write-Verbose "Use this cmdlet to deliver information about command processing."
410 | #Write-Debug "Use this cmdlet to write debug information while troubleshooting."
411 |
412 | if (!($script))
413 | {
414 | Write-Verbose -Message "[now in False]"
415 | $result = $false
416 | }
417 | else
418 | {
419 | Write-Verbose -Message "[now in True]"
420 | $result = $true
421 | }
422 |
423 | $type = $result.GetType().name
424 | Write-Verbose -Message "result is [$type]"
425 |
426 | return [bool]$result
427 |
428 | }
429 |
430 | }
431 | ```
432 |
433 |
434 |
435 |
436 |
437 | **Verify DSC Resource**
438 | 
439 |
440 |
441 | Next: _Step 2 [Create Policy](./CreatePolicy.md)_
442 |
443 |
--------------------------------------------------------------------------------
/jobscripts/ux-create_folder.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 | $result = Test-Path "/opt/FolderSetupUX01"
4 | return $result
5 | }
6 |
7 | function function_GetScript
8 | {
9 | $folder = "/opt/FolderSetupUX01"
10 | $foldercheck = Test-Path $folder
11 | $result = if (Test-Path $folder)
12 | {
13 | "Folder {0} is present" -f $folder
14 | }
15 | else {
16 | "Folder {0} is not present" -f $folder
17 | }
18 | return $result
19 | }
20 |
21 | function function_SetScript
22 | {
23 | new-item -ItemType Directory -path "/opt/FolderSetupUX01"
24 | }
--------------------------------------------------------------------------------
/jobscripts/ux-splunk_agent.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 | $result = (bash -c "ps -ef | grep -v grep | grep splunkd | wc -l") -gt 1
4 | return $result
5 | }
6 |
7 |
8 | function function_GetScript
9 | {
10 | $service = (bash -c "ps -ef | grep -v grep | grep splunkd | wc -l") -gt 1
11 | $result = if ($service)
12 | {
13 | "Splunk Deamon is running"
14 | }
15 | else
16 | {
17 | "Splunk Deamon is not installed or not running"
18 | }
19 |
20 | return $result
21 | }
22 |
23 |
24 | function function_SetScript
25 | {
26 |
27 | $downloadpath = ""
28 | $Targetlocation = "/opt/_setup/splunkforwarder"
29 | $bashfile = "SplunkForwarder.sh"
30 |
31 | # create local dir
32 | if (!(test-path $Targetlocation))
33 | {
34 | New-Item -ItemType Directory -Path $Targetlocation | Out-Null
35 | }
36 |
37 | # download content if not present
38 | $download = $Targetlocation + "/" + $bashfile
39 | if (!(Test-Path $download)) {Invoke-WebRequest -Uri $downloadpath -UseBasicParsing -OutFile $download}
40 |
41 | # test job
42 | if (Test-Path $download)
43 | {
44 | write-output "Downloaded content from [$downloadpath]"
45 | write-output "stored content to [$download]"
46 |
47 | # start setup
48 | Write-verbose ("Starting Script Execution ...")
49 |
50 | bash -c "chmod +x $download"
51 |
52 | bash -c "$download"
53 |
54 | } else {
55 | write-output "Error download content from [$downloadpath]"
56 | write-output "Error storing content to [$download]"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/jobscripts/win-create_folder.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 | $result = Test-Path "c:/FolderSetupWIN01"
4 | return $result
5 | }
6 |
7 | function function_GetScript
8 | {
9 | $folder = "c:/FolderSetupWIN01"
10 | $foldercheck = Test-Path $folder
11 | $result = if (Test-Path $folder)
12 | {
13 | "Folder {0} is present" -f $folder
14 | }
15 | else {
16 | "Folder {0} is not present" -f $folder
17 | }
18 | return $result
19 | }
20 |
21 | function function_SetScript
22 | {
23 | new-item -ItemType Directory -path "c:/FolderSetupWIN01"
24 | }
--------------------------------------------------------------------------------
/jobscripts/win-fireeye_agent.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 | $result = try {(Get-Service -Name xagt -ErrorAction SilentlyContinue).Status -eq "Running" } catch {}
4 | return $result
5 | }
6 |
7 |
8 | function function_GetScript
9 | {
10 | $getstate = try {(Get-Service -Name xagt -ErrorAction SilentlyContinue).Status -eq "Running" } catch {}
11 | $result = if ($getstate)
12 | {
13 | Write-Output ('FireEye already installed!')
14 | }
15 | else {
16 | Write-Output ('FireEye not installed or not running!')
17 | }
18 |
19 | return $result
20 | }
21 |
22 |
23 | function function_SetScript
24 | {
25 | $downloads = @( `
26 | "", `
27 | "" `
28 | )
29 | $Targetlocation = "$Env:SystemDrive\_setup\"
30 | $Installfile = "xagtSetup_xxx.msi"
31 |
32 | # create local dir
33 | if (!(test-path $Targetlocation))
34 | {
35 | New-Item -ItemType Directory -Path $Targetlocation | Out-Null
36 | }
37 |
38 | # download and check loop
39 | foreach ($download in $downloads)
40 | {
41 | # download content if not present
42 | $filepath = $null
43 | $file = $null
44 | $file = ($download.split("?")[0]).split("/")[($download.split("?")[0]).split("/").count -1]
45 | $filepath = $Targetlocation + $file
46 | if (!(Test-Path $filepath)) {Invoke-WebRequest -Uri $download -UseBasicParsing -OutFile $filepath}
47 |
48 | # test job
49 | if (Test-Path $filepath)
50 | {
51 | write-verbose -Message "Downloaded content from [$download]"
52 | write-verbose -Message "stored content to [$filepath]"
53 | }
54 | else
55 | {
56 | write-verbose -Message "Error download content from [$download]"
57 | write-verbose -Message "Error storing content to [$filepath]"
58 | }
59 | }
60 |
61 |
62 | #setup
63 |
64 | Write-verbose ("Starting Installation..")
65 | msiexec.exe /i "$TargetLocation\$Installfile" CONFJSONDIR="$TargetLocation" /qn
66 |
67 | Write-verbose ("Starting Service")
68 |
69 | $j = 1
70 | while ($j -lt 600)
71 | {
72 | $service = try {Get-Service -Name xagt -ErrorAction SilentlyContinue } catch {}
73 | if ($service) {break}
74 | $j++
75 | Start-Sleep -Milliseconds 100
76 | }
77 |
78 | Start-Sleep -Seconds 3
79 |
80 | Start-Service -Name xagt
81 |
82 | if($(Get-Service -Name xagt).Status -eq "Running" ) {
83 | Write-verbose ("FireEye installation completed.")
84 | }
85 | else {
86 | Write-verbose ("Error! FireEye installation failed.")
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/jobscripts/win-putty_setup.txt.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 | # query if putty in the desired version is installed
4 | $result = try {Get-CimInstance -Class Win32_Product | where-object IdentifyingNumber -eq "{8CFE5E4E-970A-4380-A782-AF6E609574F1}"} catch {}
5 | return $result
6 | }
7 |
8 |
9 | function function_GetScript
10 | {
11 | $getstate = try {Get-CimInstance -Class Win32_Product | where-object IdentifyingNumber -eq "{8CFE5E4E-970A-4380-A782-AF6E609574F1}"} catch {}
12 | $result = if ($getstate)
13 | {
14 | Write-Output ('PuTTY release 0.76 is installed!')
15 | }
16 | else {
17 | Write-Output ('PuTTY release 0.76 is not installed')
18 | }
19 |
20 | return $result
21 | }
22 |
23 |
24 | function function_SetScript
25 | {
26 | $downloadpath = ""
27 | $Targetlocation = "$Env:SystemDrive\_setup\"
28 | $Installfile = "putty-0.76-installer.msi"
29 |
30 | # create local dir
31 | if (!(test-path $Targetlocation))
32 | {
33 | New-Item -ItemType Directory -Path $Targetlocation | Out-Null
34 | }
35 |
36 | # download content if not present
37 | $download = $Targetlocation + $Installfile
38 | if (!(Test-Path $download)) {Invoke-WebRequest -Uri $downloadpath -UseBasicParsing -OutFile $download}
39 | # test job
40 | if (Test-Path $download)
41 | {
42 | write-verbose -Message "Downloaded content from [$downloadpath]"
43 | write-verbose -Message "stored content to [$download]"
44 |
45 | # start setup
46 | Write-verbose ("Starting Installation..")
47 | msiexec.exe /i $download /qn
48 |
49 | } else {
50 | write-verbose -Message "Error download content from [$downloadpath]"
51 | write-verbose -Message "Error storing content to [$download]"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/jobscripts/win-splunk_agent.ps1:
--------------------------------------------------------------------------------
1 | function function_TestScript
2 | {
3 |
4 | # verify config package for splunk connection
5 | $setupdir = $env:SystemDrive + "\_setup\splunk"
6 | $BlobConfig = "splunkforwarder_config.zip"
7 | $configpackage = if (test-path $setupdir\$BlobConfig) {Get-Item $setupdir\$BlobConfig}
8 | $configpackagetmp = try{(($configpackage[0].name).split(".")[0]) } catch {}
9 |
10 | # define service
11 | $service = @{}
12 | $service.Add('Name','SplunkForwarder')
13 | $service.Add('StartupType','Automatic')
14 | $service.Add('BuiltInAccount','LocalSystem')
15 | $service.Add('Status','Running')
16 | $log = $null
17 | $log = @()
18 | $output = $null
19 | $count = $null
20 |
21 | # get service
22 | $servicestatus = Get-Service -Name $service.name -ErrorAction SilentlyContinue
23 |
24 |
25 | # check states
26 | $result = $true
27 | if (!($servicestatus.StartType -eq $service.StartupType)) {$result = $null}
28 | if (!($servicestatus.Status -eq $service.Status)) {$result = $null}
29 | if (!((Get-CimInstance -ClassName Win32_Service | where Name -eq $service.name).startname -eq $service.BuiltInAccount)) {$result = $null}
30 |
31 | # verify whether is has run already or there was a new setup at this run
32 | if (!(Get-Content $setupdir\splunkforwarder_config.log -ErrorAction SilentlyContinue) -eq $configpackagetmp) {$result = $null}
33 | if (!($configpackagetmp)) {$result = $null}
34 |
35 |
36 | return $result
37 | }
38 |
39 |
40 | function function_GetScript
41 | {
42 | # verify config package for splunk connection
43 | $setupdir = $env:SystemDrive + "\_setup\splunk"
44 | $BlobConfig = "splunkforwarder_config.zip"
45 | $configpackage = if (test-path $setupdir\$BlobConfig) {Get-Item $setupdir\$BlobConfig}
46 | $configpackagetmp = try{(($configpackage[0].name).split(".")[0]) } catch {}
47 |
48 | # define service
49 | $service = @{}
50 | $service.Add('Name','SplunkForwarder')
51 | $service.Add('StartupType','Automatic')
52 | $service.Add('BuiltInAccount','LocalSystem')
53 | $service.Add('Status','Running')
54 | $log = $null
55 | $log = @()
56 | $output = $null
57 | $count = $null
58 |
59 | # get service
60 | $servicestatus = Get-Service -Name $service.name -ErrorAction SilentlyContinue
61 | $serviceaccount = (Get-CimInstance -ClassName Win32_Service | where Name -eq $service.name).startname
62 | $configpackage = Get-Content $setupdir\splunkforwarder_config.log -ErrorAction SilentlyContinue
63 |
64 | $result = "Servicename: {0} `r`nStartupType: Actual [{1}] | Expected [{2}] `r`nAccount: Actual [{3}] | Expected [{4}] `r`nState: Actual [{5}] | Expected [{6}] `r`nconfigpackage: Actual [{7}] | Expected [{8}] `
65 | " -f $service.Name, $servicestatus.StartType, $service.StartupType, $serviceaccount, $service.BuiltInAccount, $servicestatus.Status , $service.Status, $configpackagetmp, $configpackage
66 |
67 |
68 |
69 | return $result
70 | }
71 |
72 |
73 | function function_SetScript
74 | {
75 | # variable for download
76 | $StorageAccountName = ""
77 | $Container = "install"
78 | $Blob = "splunkforwarder.msi"
79 | $BlobConfig = "splunkforwarder_config.zip"
80 | $SASToken = ""
81 | $SASTokenConfig = ""
82 | $setupdir = $env:SystemDrive + "\_setup\splunk"
83 |
84 | # variable for ClientName config directive
85 | $dcconf = $env:ProgramFiles + "\SplunkUniversalForwarder\etc\system\local\deploymentclient.conf"
86 |
87 | # variable for config package setup
88 | $splunkdir = $env:ProgramFiles+"\SplunkUniversalForwarder"
89 |
90 |
91 | $service = @{}
92 | $service.Add('Name','SplunkForwarder')
93 | $service.Add('StartupType','Automatic')
94 | $service.Add('BuiltInAccount','LocalSystem')
95 | $service.Add('Status','Running')
96 | $log = $null
97 | $log = @()
98 | $output = $null
99 | $count = $null
100 |
101 |
102 | # get service
103 | $servicestatus = Get-Service -Name $service.name -ErrorAction SilentlyContinue
104 |
105 | # verify setupdir and create it
106 | if (!(test-path $setupdir)) {new-item -ItemType Directory -Path $setupdir}
107 |
108 | # verify service install state and run setup if required
109 | if (!($servicestatus))
110 | {
111 | # download
112 | if (!(test-path $($setupdir+"\"+$blob))) {Invoke-WebRequest -Uri "https://$StorageAccountName.blob.core.windows.net/$Container/$($Blob)$($SASToken)" -OutFile "$setupdir\$Blob"}
113 |
114 | # MSI install
115 | msiexec.exe /i "$setupdir\$Blob" DEPLOYMENT_SERVER="" SPLUNKUSERNAME="" GENRANDOMPASSWORD="" AGREETOLICENSE=Yes REBOOT=R /l*v $setupdir\splunkforwarder.log /qn
116 |
117 | # log
118 | $log += "MSI setup = done"
119 |
120 | # load service status
121 | $count = $null
122 | while (!($servicestatus = Get-Service -Name $service.name -ErrorAction SilentlyContinue))
123 | {
124 | start-sleep -Seconds 5
125 | $count++
126 | if ($count -ge 24)
127 | {
128 | $log += "service not found after setup " + $service.name
129 | break
130 | }
131 |
132 | }
133 |
134 | }
135 | else
136 | {
137 | $log += "software installed, skip setup"
138 | }
139 |
140 |
141 | # verify service startup type and correct it
142 | if (!($servicestatus.StartType -eq $service.StartupType))
143 | {
144 | # correct service startup
145 | Set-Service -Name $service.Name -StartupType $service.StartupType
146 |
147 | # verify action
148 | if (((get-service -Name $service.name).StartType) -eq $service.StartupType)
149 | {
150 | #log
151 | $log += "corrected startuptype to " + $service.StartupType
152 | }
153 | else
154 | {
155 | #log
156 | $log += "error correcting startuptype to " + $service.StartupType
157 | }
158 | }
159 | else
160 | {
161 | #log
162 | $log += "OK startuptype " + $service.StartupType
163 |
164 | }
165 |
166 |
167 | # verify service status and correct it
168 | if (!($servicestatus.Status -eq $service.Status))
169 | {
170 | # correct service startup
171 | Set-Service -Name $service.Name -Status $service.Status -WarningAction SilentlyContinue
172 |
173 | # verify action
174 | if (((get-service -Name $service.name).Status) -eq $service.Status)
175 | {
176 | #log
177 | $log += "corrected status to " + $service.status
178 | }
179 | else
180 | {
181 | #log
182 | $log += "error correcting stats to " + $service.Status
183 | }
184 | }
185 | else
186 | {
187 | #log
188 | $log += "OK status " + $service.status
189 |
190 | }
191 |
192 |
193 | # verify service account
194 | if (!((Get-CimInstance -ClassName Win32_Service | where Name -eq $service.name).startname -eq $service.BuiltInAccount))
195 | {
196 | #log
197 | $log += "correct manually, service runs as " + (Get-CimInstance -ClassName Win32_Service | where Name -eq $service.name).startname
198 | }
199 | else
200 | {
201 | #log
202 | $log += "OK service account " + (Get-CimInstance -ClassName Win32_Service | where Name -eq $service.name).startname
203 | }
204 |
205 |
206 |
207 | # Add clientName configuration directive
208 |
209 | if (!( test-path $($dcconf) ))
210 | {
211 | Add-Content -Path $dcconf -Value '[deployment-client]'
212 | Add-Content -Path $dcconf -Value 'clientName = '
213 | $log += "Auto-Onboarding config installed and deploymentclient.conf created."
214 | }
215 | else
216 | {
217 | if ( (Get-Content $dcconf | Select-String -Pattern 'clientName' ).Matches.Success )
218 | {
219 | $log += "Auto-Onboarding config already present, not touching."
220 | } else {
221 | Add-Content -Path $dcconf -Value '[deployment-client]'
222 | Add-Content -Path $dcconf -Value 'clientName = '
223 | $log += "Auto-Onboarding config installed."
224 | }
225 | }
226 |
227 |
228 |
229 | # download
230 | if (!(test-path $($setupdir+"\"+$BlobConfig))) {Invoke-WebRequest -Uri "https://$StorageAccountName.blob.core.windows.net/$Container/$($BlobConfig)$($SASTokenConfig)" -OutFile "$setupdir\$BlobConfig"}
231 |
232 |
233 | # install config package for splunk connection
234 | $configpackage = Get-Item $setupdir\$BlobConfig
235 | $configpackagetmp = (($configpackage[0].name).split(".")[0])
236 |
237 | # verify whether is has run already or there was a new setup at this run
238 | if (!((Get-Content $setupdir\splunkforwarder_config.log -ErrorAction SilentlyContinue) -eq $configpackagetmp) -or $count )
239 |
240 | {
241 | # extract zip
242 | # Expand-Archive $configpackage[0] -DestinationPath $setupdir\$configpackagetmp
243 | # Expand-Archive is not available in Srv2012, PS4
244 |
245 | Add-Type -assembly 'system.io.compression.filesystem'
246 | [io.compression.zipfile]::ExtractToDirectory($configpackage[0],"$setupdir\$configpackagetmp")
247 |
248 |
249 | # get file to copy
250 | $items = Get-childItem $setupdir\$configpackagetmp -Recurse | where mode -Match "-a---"
251 |
252 | # copy files to splunk directory
253 | foreach ($item in $items)
254 | {
255 | $target = -join ($splunkdir, $item.directoryname.Replace("$($setupdir)\$($configpackagetmp)",""))
256 | if (!(test-path $target)) {New-Item -ItemType Directory $target | Out-Null}
257 | Copy-Item -Path $item.FullName $target -Force
258 | }
259 |
260 | # delete configpackage temp
261 | remove-item -Path $setupdir\$configpackagetmp\* -Recurse -Force -Confirm:$false
262 | remove-item -Path $setupdir\$configpackagetmp\ -Force -Confirm:$false
263 |
264 | # write package details to a log
265 | $configpackagetmp | out-file $setupdir\splunkforwarder_config.log
266 |
267 | # stop service and set to configured state to read new config
268 | Set-Service -Name $service.Name -Status Stopped -WarningAction SilentlyContinue
269 | Set-Service -Name $service.Name -Status $service.Status -WarningAction SilentlyContinue
270 |
271 | # log
272 | $log += "just installed config package " + $configpackagetmp
273 | }
274 | else
275 | {
276 | $log += "configpackage installed already " + $configpackagetmp
277 | }
278 |
279 |
280 | # log output
281 | foreach ($entry in $log)
282 | {
283 | $output = $output + $entry + ";"
284 | }
285 |
286 | write-host $output.TrimEnd(";")
287 |
288 | }
289 |
290 |
--------------------------------------------------------------------------------
/pics/ARG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/ARG.png
--------------------------------------------------------------------------------
/pics/ARGdetails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/ARGdetails.png
--------------------------------------------------------------------------------
/pics/MDC01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/MDC01.png
--------------------------------------------------------------------------------
/pics/MDC02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/MDC02.png
--------------------------------------------------------------------------------
/pics/MDC03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/MDC03.png
--------------------------------------------------------------------------------
/pics/MDC04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/MDC04.png
--------------------------------------------------------------------------------
/pics/REST01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/REST01.png
--------------------------------------------------------------------------------
/pics/REST02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/REST02.png
--------------------------------------------------------------------------------
/pics/RecARG1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/RecARG1.png
--------------------------------------------------------------------------------
/pics/RecARG2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/RecARG2.png
--------------------------------------------------------------------------------
/pics/RecLAW.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/RecLAW.png
--------------------------------------------------------------------------------
/pics/Workbook1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/Workbook1.png
--------------------------------------------------------------------------------
/pics/Workbook2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/Workbook2.png
--------------------------------------------------------------------------------
/pics/Workbook3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/Workbook3.png
--------------------------------------------------------------------------------
/pics/Workbook4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/Workbook4.png
--------------------------------------------------------------------------------
/pics/Workbook5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/Workbook5.png
--------------------------------------------------------------------------------
/pics/blobsasgeneration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/blobsasgeneration.png
--------------------------------------------------------------------------------
/pics/initiativebasics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativebasics.png
--------------------------------------------------------------------------------
/pics/initiativecreate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativecreate.png
--------------------------------------------------------------------------------
/pics/initiativegroups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativegroups.png
--------------------------------------------------------------------------------
/pics/initiativeparameter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativeparameter.png
--------------------------------------------------------------------------------
/pics/initiativepolicies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativepolicies.png
--------------------------------------------------------------------------------
/pics/initiativepolicies2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativepolicies2.png
--------------------------------------------------------------------------------
/pics/initiativesaved.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/initiativesaved.png
--------------------------------------------------------------------------------
/pics/installmoduleGC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/installmoduleGC.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitative.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitative2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitative2.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitativebasics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitativebasics.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitativeidentity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitativeidentity.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitativerem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitativerem.png
--------------------------------------------------------------------------------
/pics/mdcaddcustominitativeremtask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcaddcustominitativeremtask.png
--------------------------------------------------------------------------------
/pics/mdcenvsettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/mdcenvsettings.png
--------------------------------------------------------------------------------
/pics/policyazure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policyazure.png
--------------------------------------------------------------------------------
/pics/policycompliance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policycompliance.png
--------------------------------------------------------------------------------
/pics/policycompliancepolicies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policycompliancepolicies.png
--------------------------------------------------------------------------------
/pics/policyrescompliance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policyrescompliance.png
--------------------------------------------------------------------------------
/pics/policyrescomplianceoverview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policyrescomplianceoverview.png
--------------------------------------------------------------------------------
/pics/policyrescomplianceoverview2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/policyrescomplianceoverview2.png
--------------------------------------------------------------------------------
/pics/powershell-azpolicy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/powershell-azpolicy.png
--------------------------------------------------------------------------------
/pics/powershell-azpolicyresult.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/powershell-azpolicyresult.png
--------------------------------------------------------------------------------
/pics/solution-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/solution-overview.png
--------------------------------------------------------------------------------
/pics/verifydscresource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HolgerWache/MachineConfiguration/ec4abd2dd6cdf86d876725424874c93667cc55fd/pics/verifydscresource.png
--------------------------------------------------------------------------------