· 6 years ago · Sep 19, 2019, 09:04 AM
1<#
2.SYNOPSIS
3 The synopsis goes here. This can be one line, or many.
4 This version of the template has inbuilt functions to capture credentials and store it securely for reuse
5 Avoids the need to have plaintext passwords in the script
6
7.DESCRIPTION
8 The description is usually a longer, more detailed explanation of what the script or function does.
9 Take as many lines as you need.
10
11 There are two main components of this script:
12 1. Prepare Credentials
13 This phase is run at least once. It generates a secure hashed credential file that can be used by the script.
14 Re-running this phase will overwrite the existing credential file.
15 There are two modes provided:
16 - DPAPI: Recommended. More secure. Requires both 'PrepareCredentials' and 'Execution' phases to be run by the same user account on the same machine to be able to decrypt key
17 - AES: Only to be used when service account cannot be used to run in interactive mode (and thus can't run PrepareCredentials). The AES.Key file that is generated is decryptable by anyone, so you must protect read access to this file using ACLs.
18
19 2. Execution
20 This phase is used to actually perform the exection of this script. 'Prepare Credentials' must have been run at least once.
21
22 Author: David Lee (david.lee@kloud.com.au)
23 Change Log:
24 07/12/15 0.1 DL Initial Release
25
26.PARAMETER PrepareCredentials
27 Run with this switch at least once. Two methods are provided for generating the secure hashed credential file based on your requirements.
28
29.PARAMETER Execution
30 Run this switch when you want to run this script normally
31
32.PARAMETER param1
33 These parameters are automatched against the parameters defined in this script
34
35.PARAMETER param2
36 Use a .parameter for each one of your parameters
37
38.EXAMPLE
39 .\script-name.ps1 -PrepareCredentials
40
41.EXAMPLE
42 .\script-name.ps1 -Execution -param1 value1 -param2 value2
43
44#>
45[CmdletBinding()]
46Param([switch]$PrepareCredentials, [switch]$Execution, $param1, $param2)
47
48<##====================================================================================
49 GLOBAL CONFIGURATION
50##===================================================================================#>
51$erroractionpreference = "stop"
52
53$debugFlag = $PSBoundParameters.Debug.IsPresent
54$verboseFlag = $PSBoundParameters.Verbose.IsPresent
55
56# Use this to control whether to break on debugs or not
57if($debugFlag -eq $true)
58{
59 # If you want a break prompt on each Write-Debug entry, use "inquire"
60 # Otherwise use "continue" to simply output debug log (recommended)
61 $debugPreference = "continue"
62}
63
64# This will be used in file paths below, so avoid using spaces and special characters
65$scriptName = "Script_Name"
66
67# Root Folder
68$rootFolder = "C:\Scripts\$scriptName"
69
70# Secure Credential File
71$credentialFileDir = $rootFolder
72$credentialFilePath = "$credentialFileDir\$scriptName-SecureStore.txt"
73
74# Path to store AES File (if using AES mode for PrepareCredentials)
75$AESKeyFileDir = $rootFolder
76$AESKeyFilePath = "$AESKeyFileDir\$scriptName-AES.key"
77
78# Output Log File Name: DescriptiveName_yyyy-MM-ddTHHmm.log
79$outputLogFileName = "$scriptName_" + (get-date -f yyyy-MM-ddTHHmm) + ".log"
80
81# Change Log File Directory as necessary
82$outputLogDir = "$rootFolder\Logs"
83# Full Path of Log File
84$outputLogPath = "$outputLogDir\$outputLogFileName"
85
86# Log Age Limit (in days). Log files older than this will be auto deleted
87$logAgeLimit = 30
88
89# This is where I put global variables and the like for easy access an updates
90
91<##====================================================================================
92 FUNCTIONS
93##===================================================================================#>
94
95<#
96.SYNOPSIS
97 Initializes the output log file.
98
99.DESCRIPTION
100 Execute this function at the beginning of your main code to ensure the log file path
101 exists, and if it doesn't, create the folder paths to ensure future Write-Log calls
102 will run without issue
103
104 This function can also be modified to create log file headers
105
106.PARAMETER overwrite
107 Set this flag to overwrite any existing log files. Otherwise default is to append
108 to the existing log file
109
110.PARAMETER headerText
111 Optional ability to define some text at the start of eveyr initialization of the log file
112 Useful if you are not overwriting the log file each time script is run]
113#>
114function Start-LogFile([switch]$overwrite, $headerText)
115{
116 # Check if log dir exists, if not, create it
117 if(!(Test-Path $outputLogDir))
118 {
119 New-Item -type Directory $outputLogDir | out-null
120 }
121 Write-Output $headerText
122
123 try
124 {
125 if($overwrite -eq $true)
126 {
127 set-content $outputLogPath $headerText
128 }
129 else
130 {
131 add-content $outputLogPath $headerText
132 }
133 }
134 catch
135 {
136 Write-Warning "Could not initialize the log file: $outputLogPath"
137 }
138}
139
140<#
141.SYNOPSIS
142 Writes to an entry into the output log file, and to the console if -Verbose is used
143
144.DESCRIPTION
145 This is a useful function for log file outputs. Change the structure of this output
146 as necessary for your scripts.
147 If the -Verbose flag is set, then the log file message will also be written to the console
148 using the Write-Verbose command
149
150.PARAMETER type
151 Optional, but can use to tag the log entry type. Recommend to use one of INFO,WARNING,ERROR
152 Will default to INFO if non specified.
153 NOTE: If you wish to write a DEBUG log entry, use the Write-DebugLog function
154
155.PARAMETER message
156 The text to write to your ouput file
157#>
158function Write-Log($message, $type)
159{
160 if($type -eq $null -or $type -eq "")
161 {
162 $type = "INFO"
163 }
164
165 try
166 {
167 # Log Entry Structure: [Date] [TYPE] [MESSAGE]
168 $logEntry = (Get-Date -format u) + "`t" + $type.ToUpper() + "`t" + $message
169 if($type -eq "WARNING")
170 {
171 Write-Host -foregroundcolor Yellow $logEntry
172 }
173 elseif($type -eq "ERROR")
174 {
175 Write-Host -foregroundcolor Red $logEntry
176 }
177 else
178 {
179 Write-Host $logEntry
180 }
181 Add-Content $outputLogPath $logEntry
182 }
183 catch
184 {
185 Write-Warning "Could not write entry to output log file: $outputLogPath `nLog Entry:$message"
186 }
187}
188
189<#
190.SYNOPSIS
191 Writes to an entry ino the output log file only if the -debug parameter was set in the script
192
193.DESCRIPTION
194 This is a useful function for performing debug logging. It will use both the in built Write-Debug
195 function as well as creating an entry to the log file with a DEBUG type
196
197.PARAMETER message
198 The debug text to write to your ouput file
199#>
200function Write-DebugLog($message)
201{
202 Write-Debug $message
203 try
204 {
205 # Only write to the log file if the -Debug parameter has been set
206 if($script:debugFlag -eq $true)
207 {
208 # Log Entry Structure: [Date] [TYPE] [MESSAGE]
209 $logEntry = (Get-Date -format u) + "`t" + "DEBUG" + "`t" + $message
210 Add-Content $outputLogPath $logEntry
211 }
212 }
213 catch
214 {
215 Write-Warning "Could not write entry to output log file: $outputLogPath `nLog Entry:$message"
216 }
217}
218
219<#
220.SYNOPSIS
221 Cleans up log files
222
223.DESCRIPTION
224 This is a useful function when scripts are run regularly and thus create lots of log files.
225 Based on a log age date, it will remove all log files older than that period
226 Uses a Global variable for the log age date
227
228 .PARAMETER logPath
229 Folder location for log files to remove
230
231 .PARAMETER fileExtension
232 Type of files to delete. Use a wildcard format like "*.log"
233#>
234function Cleanup-LogFiles($logPath, $fileExtension)
235{
236 # Determine the date of which files older than specific period will be deleted
237 $dateToDelete = (Get-Date).AddDays(-$logAgeLimit)
238 $filesToDelete = Get-ChildItem $logPath -Include $fileExtension -Recurse | where{$_.LastWriteTime -le $dateToDelete}
239 foreach($file in $filesToDelete)
240 {
241 $filePath = $file.FullName
242 try
243 {
244 Write-DebugLog "Deleting $file..."
245 Remove-Item $filePath -force | out-null
246 }
247 catch
248 {
249 Write-Log "Failed to delete old log file ($filePath)" -type WARNING
250 }
251 }
252}
253
254<##====================================================================================
255 MAIN CODE
256##===================================================================================#>
257
258# Checks to make sure at least either PrepareCredentials or Execution switches are run and not both.
259if(($PrepareCredentials -eq $false -and $Execution -eq $false) -or ($PrepareCredentials -eq $true -and $Execution -eq $true))
260{
261 Write-Host -foreground red "[ERROR] You must specify either -PrepareCredentials or -Execution"
262 exit -1
263}
264
265# Leave this code as is. This is the code to generate the secure files.
266if($PrepareCredentials -eq $true)
267{
268 try
269 {
270
271 $headerText = (Get-Date -format u) + "`t" + "INIT" + "`t" + "****$scriptName log initialised in PrepareCredentials mode.****"
272 Start-LogFile -headerText $headerText # Initialize the log file with a header
273
274 # Provide two
275 $title = "Prepare Credentials Encryption Method"
276 $message = "Which mode do you wish to use?"
277
278 $DPAPI = New-Object System.Management.Automation.Host.ChoiceDescription "&DPAPI", `
279 "Use Windows Data Protection API. This uses your current user context and machine to create the encryption key."
280
281 $AES = New-Object System.Management.Automation.Host.ChoiceDescription "&AES", `
282 "Use a randomly generated SecureKey for AES. This will generate an AES.key file that you need to protect as it contains the encryption key."
283
284 $options = [System.Management.Automation.Host.ChoiceDescription[]]($DPAPI, $AES)
285
286 $choice = $host.ui.PromptForChoice($title, $message, $options, 0)
287
288 switch ($choice)
289 {
290 0 {$encryptMode = "DPAPI"}
291 1 {$encryptMode = "AES"}
292 }
293 Write-DebugLog "Encryption mode $encryptMode was selected to prepare the credentials."
294
295 Write-Log "Collecting Credentials to create a secure credential file..." -Type INFO
296 # Collect the credentials to be used.
297 $creds = Get-Credential
298
299 # Store the details in a hashed format
300 $userName = $creds.UserName
301 $passwordSecureString = $creds.Password
302
303 if($encryptMode -eq "DPAPI")
304 {
305 $password = $passwordSecureString | ConvertFrom-SecureString
306 }
307 elseif($encryptMode -eq "AES")
308 {
309 # Generate a random AES Encryption Key.
310 $AESKey = New-Object Byte[] 32
311 [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
312
313 # Store the AESKey into a file. This file should be protected! (e.g. ACL on the file to allow only select people to read)
314
315 # Check if Credential File dir exists, if not, create it
316 if(!(Test-Path $AESKeyFileDir))
317 {
318 New-Item -type Directory $AESKeyFileDir | out-null
319 }
320 Set-Content $AESKeyFilePath $AESKey # Any existing AES Key file will be overwritten
321 $password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey
322 }
323 else
324 {
325 # Placeholder in case there are other EncryptModes
326 }
327
328
329 # Check if Credential File dir exists, if not, create it
330 if(!(Test-Path $credentialFileDir))
331 {
332 New-Item -type Directory $credentialFileDir | out-null
333 }
334
335 # Contents in this file can only be read and decrypted if you have the encryption key
336 # If using DPAPI mode, then this can only be ready by the user that ran this script on the same machine
337 # If using AES mode, then the AES.key file contains the encryption key
338
339 Set-Content $credentialFilePath $userName # Any existing credential file will be overwritten
340 Add-Content $credentialFilePath $password
341
342 if($encryptMode -eq "AES")
343 {
344 Write-Host -foreground Yellow "IMPORTANT! Make sure you restrict read access, via ACLs, to the AES.Key file that has been generated to ensure stored credentials are secure."
345 }
346
347 Write-Log "Credentials collected and stored." -Type INFO
348 Write-Host -foreground Green "Credentials collected and stored."
349 }
350 catch
351 {
352 $errText = $error[0]
353 Write-Log "Failed to Prepare Credentials. Error Message was: $errText" -type ERROR
354 Write-Host -foreground red "Failed to Prepare Credentials. Please check Log File."
355 Exit -1
356 }
357}
358
359
360if($Execution -eq $true)
361{
362
363 $headerText = (Get-Date -format u) + "`t" + "INIT" + "`t" + "****$scriptName log initialised in Execution mode****"
364 Start-LogFile -headerText $headerText # Initialize the log file with a header
365
366 # Check to ensure we have a secure credential file (i.e. -PrepareCredentials has been run) and that the contents are valid
367 if(!(Test-Path $credentialFilePath))
368 {
369 Write-Log "Could not find a secure credential file at $credentialFilePath. Exiting." -Type ERROR
370 Write-Host -foreground red "[ERROR] Could not find a secure credential file at $credentialFilePath. Ensure that you have run the -PrepareCredentials parameter at least once for this script."
371 exit -1
372 }
373
374 # Check to see if we have an AES Key file. If so, then we will use it to decrypt the secure credential file
375 if(Test-Path $AESKeyFilePath)
376 {
377 try
378 {
379 Write-DebugLog "Found an AES Key File. Using this to decrypt the secure credential file."
380 $decryptMode = "AES"
381 $AESKey = Get-Content $AESKeyFilePath
382 }
383 catch
384 {
385 $errText = $error[0]
386 Write-Log "AES Key file detected, but could not be read. Error Message was: $errText" -type ERROR
387 exit -1
388 }
389 }
390 else
391 {
392 Write-DebugLog "No AES Key File found. Using DPAPI method, which requires same user and machine to decrypt the secure credential file."
393 $decryptMode = "DPAPI"
394 }
395
396 try
397 {
398 Write-DebugLog "Reading secure credential file at $credentialFilePath."
399 $credFiles = Get-Content $credentialFilePath
400 $userName = $credFiles[0]
401 if($decryptMode -eq "DPAPI")
402 {
403 $password = $credFiles[1] | ConvertTo-SecureString
404 }
405 elseif($decryptMode -eq "AES")
406 {
407 $password = $credFiles[1] | ConvertTo-SecureString -Key $AESKey
408 }
409 else
410 {
411 # Placeholder in case there are other decrypt modes
412 }
413
414 Write-DebugLog "Creating credential object..."
415 $credObject = New-Object System.Management.Automation.PSCredential -ArgumentList $userName, $password
416 $passwordClearText = $credObject.GetNetworkCredential().Password
417 Write-DebugLog "Credential store read. UserName is $userName and Password is $passwordClearText"
418
419
420
421 #**** Put Main Code here. The credentials you have stored is available as $credObject ****
422
423
424 # Example Log File Entry
425 Write-Log "No Type Flag creates an INFO entry"
426 Write-Log "Or you can create a WARNING entry" -type "WARNING"
427 Write-Log "Or an ERROR entry" -type "ERROR"
428
429 # Example Debug Log
430 Write-DebugLog "This writes stuff if the -Debug flag is set in the script"
431
432
433 }
434 catch
435 {
436 $errText = $error[0]
437 Write-Log "Could not execute in Execution mode. Error Message was: $errText" -type ERROR
438 exit -1
439 }
440
441}
442
443<##====================================================================================
444 END OF CODE
445##===================================================================================#>