]> insang Git - wevape-lu1_pos.git/blob
f50a7df3c82fd18546a4ab098239c428697c1dfd
[wevape-lu1_pos.git] /
1 # A global variable that contains localized messages.
2 data LocalizedData
3 {
4 # culture="en-US"
5 ConvertFrom-StringData @'
6 UserWithName=User: {0}
7 RemoveOperation=Remove
8 AddOperation=Add
9 SetOperation=Set
10 ConfigurationStarted=Configuration of user {0} started.
11 ConfigurationCompleted=Configuration of user {0} completed successfully.
12 UserCreated=User {0} created successfully.
13 UserUpdated=User {0} properties updated successfully.
14 UserRemoved=User {0} removed successfully.
15 NoConfigurationRequired=User {0} exists on this node with the desired properties. No action required.
16 NoConfigurationRequiredUserDoesNotExist=User {0} does not exist on this node. No action required.
17 InvalidUserName=The name {0} cannot be used. Names may not consist entirely of periods and/or spaces, or contain these characters: {1}
18 UserExists=A user with the name {0} exists.
19 UserDoesNotExist=A user with the name {0} does not exist.
20 PropertyMismatch=The value of the {0} property is expected to be {1} but it is {2}.
21 PasswordPropertyMismatch=The value of the {0} property does not match.
22 AllUserPropertisMatch=All {0} {1} properties match.
23 ConnectionError = There could be a possible connection error while trying to use the System.DirectoryServices API's.
24 MultipleMatches = There could be a possible multiple matches exception while trying to use the System.DirectoryServices API's.
25 '@
26 }
27
28 Import-LocalizedData LocalizedData -FileName MSFT_UserResource.strings.psd1
29
30 Import-Module "$PSScriptRoot\..\DSCResourceHelper.psm1"
31
32 if (-not (IsNanoServer))
33 {
34     Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement'
35 }
36
37 <#
38 .Synopsys
39 The Get-TargetResource cmdlet.
40 #>
41 function Get-TargetResource
42 {
43     param
44     (
45         [parameter(Mandatory = $true)]
46         [ValidateNotNullOrEmpty()]
47         [System.String]
48         $UserName
49     )
50
51     if (IsNanoServer)
52     {
53         Get-TargetResourceOnNanoServer @PSBoundParameters
54     }
55     else
56     {
57         Get-TargetResourceOnFullSKU @PSBoundParameters
58     }
59 }
60
61 <#
62 .Synopsys
63 The Set-TargetResource cmdlet.
64 #>
65 function Set-TargetResource
66 {
67     [CmdletBInding(SupportsShouldProcess=$true)]
68     param
69     (
70         [parameter(Mandatory = $true)]
71         [ValidateNotNullOrEmpty()]
72         [System.String]
73         $UserName,
74
75         [ValidateSet("Present", "Absent")]
76         [System.String]
77         $Ensure = "Present",
78
79         [System.String]
80         $FullName,
81
82         [System.String]
83         $Description,
84
85         [ValidateNotNullOrEmpty()]
86         [System.Management.Automation.PSCredential]
87         $Password,
88
89         [System.Boolean]
90         $Disabled,
91
92         [System.Boolean]
93         $PasswordNeverExpires,
94
95         [System.Boolean]
96         $PasswordChangeRequired,
97
98         [System.Boolean]
99         $PasswordChangeNotAllowed
100     )
101
102     if (IsNanoServer)
103     {
104         Set-TargetResourceOnNanoServer @PSBoundParameters
105     }
106     else
107     {
108         Set-TargetResourceOnFullSKU @PSBoundParameters
109     }
110 }
111
112 <#
113 .Synopsys
114 The Test-TargetResource cmdlet is used to validate if the resource is in a state as expected in the instance document.
115 #>
116 function Test-TargetResource
117 {
118     param
119     (
120         [parameter(Mandatory = $true)]
121         [ValidateNotNullOrEmpty()]
122         [System.String]
123         $UserName,
124
125         [ValidateSet("Present", "Absent")]
126         [System.String]
127         $Ensure = "Present",
128
129         [System.String]
130         $FullName,
131
132         [System.String]
133         $Description,
134
135         [ValidateNotNullOrEmpty()]
136         [System.Management.Automation.PSCredential]
137         $Password,
138
139         [System.Boolean]
140         $Disabled,
141
142         [System.Boolean]
143         $PasswordNeverExpires,
144
145         [System.Boolean]
146         $PasswordChangeRequired,
147
148         [System.Boolean]
149         $PasswordChangeNotAllowed
150     )
151
152     if (IsNanoServer)
153     {
154         Test-TargetResourceOnNanoServer @PSBoundParameters
155     }
156     else
157     {
158         Test-TargetResourceOnFullSKU @PSBoundParameters
159     }
160 }
161
162
163 <#
164 .Synopsys
165 The Get-TargetResource cmdlet.
166 #>
167 function Get-TargetResourceOnFullSKU
168 {
169     param
170     (
171         [parameter(Mandatory = $true)]
172         [ValidateNotNullOrEmpty()]
173         [System.String]
174         $UserName
175     )
176
177     Set-StrictMode -Version Latest
178
179     ValidateUserName -UserName $UserName
180
181     # Try to find a user by a name.
182     $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)
183
184     try
185     {
186         $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
187         if($user -ne $null)
188         {
189             # The user is found. Return all user properties and Ensure="Present".
190             $returnValue = @{
191                                 UserName = $user.Name; 
192                                 Ensure = "Present";
193                                 FullName = $user.DisplayName;
194                                 Description = $user.Description;
195                                 Disabled = -not $user.Enabled;
196                                 PasswordNeverExpires = $user.PasswordNeverExpires;
197                                 PasswordChangeRequired = $null;
198                                 PasswordChangeNotAllowed = $user.UserCannotChangePassword;
199                             }
200
201             return $returnValue;
202         }
203
204         # The user is not found. Return Ensure=Absent.
205         return @{
206                     UserName = $UserName; 
207                     Ensure = "Absent";
208                 }
209     }
210     catch
211     {
212          ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_)
213     }
214     finally
215     {
216         if($user -ne $null)
217         {
218             $user.Dispose();
219         }
220
221         $principalContext.Dispose();
222     }
223 }
224
225 <#
226 .Synopsys
227 The Set-TargetResource cmdlet.
228 #>
229 function Set-TargetResourceOnFullSKU
230 {
231     [CmdletBInding(SupportsShouldProcess=$true)]
232     param
233     (
234         [parameter(Mandatory = $true)]
235         [ValidateNotNullOrEmpty()]
236         [System.String]
237         $UserName,
238
239         [ValidateSet("Present", "Absent")]
240         [System.String]
241         $Ensure = "Present",
242
243         [System.String]
244         $FullName,
245
246         [System.String]
247         $Description,
248
249         [ValidateNotNullOrEmpty()]
250         [System.Management.Automation.PSCredential]
251         $Password,
252
253         [System.Boolean]
254         $Disabled,
255
256         [System.Boolean]
257         $PasswordNeverExpires,
258
259         [System.Boolean]
260         $PasswordChangeRequired,
261
262         [System.Boolean]
263         $PasswordChangeNotAllowed
264     )
265
266     Set-StrictMode -Version Latest
267
268     Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName)
269
270     ValidateUserName -UserName $UserName
271
272
273     # Try to find a user by a name.
274     $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)
275
276     try
277     {
278         $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
279         if($Ensure -eq "Present")
280         {
281             # Ensure is set to "Present".
282
283             $whatIfShouldProcess = $true;
284             $userExists = $false;
285             $saveChanges = $false;
286
287             if($user -eq $null)
288             {
289                 # A user does not exist. Check WhatIf for adding a user.
290                 $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.AddOperation);
291             }
292             else
293             {
294                 # A user exists.
295                 $userExists = $true;
296
297                 # Check WhatIf for setting a user.
298                 $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.SetOperation);
299             }
300
301             if($whatIfShouldProcess)
302             {
303                 if(-not $userExists)
304                 {
305                     # The user with the provided name does not exist. Add a new user.
306                     $user = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $principalContext
307                     $user.Name = $UserName;
308                     $saveChanges = $true;
309                 }
310
311                 # Set user properties.
312                 if($PSBoundParameters.ContainsKey('FullName') -and (-not $userExists -or $FullName -ne $user.DisplayName))
313                 {
314                     $user.DisplayName = $FullName;
315                     $saveChanges = $true;
316                 }
317                 else
318                 {
319                     if(-not $userExists)
320                     {
321                         # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name.
322                         $user.DisplayName = [String]::Empty;
323                     }
324                 }
325
326                 if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description))
327                 {
328                     $user.Description = $Description;
329                     $saveChanges = $true;
330                 }
331
332                 # Password. Set the password regardless of the state of the user.
333                 if($PSBoundParameters.ContainsKey('Password'))
334                 {
335                     $user.SetPassword($Password.GetNetworkCredential().Password);
336                     $saveChanges = $true;
337                 }
338
339                 if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled))
340                 {
341                     $user.Enabled = -not $Disabled;
342                     $saveChanges = $true;
343                 }
344
345                 if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or $PasswordNeverExpires -ne $user.PasswordNeverExpires))
346                 {
347                     $user.PasswordNeverExpires = $PasswordNeverExpires;
348                     $saveChanges = $true;
349                 }
350
351                 if($PSBoundParameters.ContainsKey('PasswordChangeRequired'))
352                 {
353                     if($PasswordChangeRequired)
354                     {
355                         # Expire the password. This will force the user to change the password at the next logon.
356                         $user.ExpirePasswordNow();
357                         $saveChanges = $true;
358                     }
359                 }
360
361                 if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword))
362                 {
363                     $user.UserCannotChangePassword = $PasswordChangeNotAllowed;
364                     $saveChanges = $true;
365
366                 }
367
368                 if($saveChanges)
369                 {
370                     $user.Save();
371
372                     # Send an operation success verbose message.
373                     if($userExists)
374                     {
375                         Write-Verbose -Message ($LocalizedData.UserUpdated -f $UserName)
376                     }
377                     else
378                     {
379                         Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName)
380                     }
381                 }
382                 else
383                 {
384                     Write-Verbose -Message ($LocalizedData.NoConfigurationRequired -f $UserName)
385                 }
386             }
387         }
388         else
389         {
390             # Ensure is set to "Absent".
391             if($user -ne $null)
392             {
393                 # The user exists.
394                 if($pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.RemoveOperation))
395                 {
396                     # Remove the user by the provided name.
397                     $user.Delete();
398                 }
399
400                 Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName)
401             }
402             else
403             {
404                 Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName)
405             }
406         }
407     }
408     catch
409     {
410          ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_)
411     }
412     finally
413     {
414         if($user -ne $null)
415         {
416             $user.Dispose();
417         }
418
419         $principalContext.Dispose();
420     }
421
422     Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName)
423 }
424
425 <#
426 .Synopsys
427 The Test-TargetResource cmdlet is used to validate if the resource is in a state as expected in the instance document.
428 #>
429 function Test-TargetResourceOnFullSKU
430 {
431     param
432     (
433         [parameter(Mandatory = $true)]
434         [ValidateNotNullOrEmpty()]
435         [System.String]
436         $UserName,
437
438         [ValidateSet("Present", "Absent")]
439         [System.String]
440         $Ensure = "Present",
441
442         [System.String]
443         $FullName,
444
445         [System.String]
446         $Description,
447
448         [ValidateNotNullOrEmpty()]
449         [System.Management.Automation.PSCredential]
450         $Password,
451
452         [System.Boolean]
453         $Disabled,
454
455         [System.Boolean]
456         $PasswordNeverExpires,
457
458         [System.Boolean]
459         $PasswordChangeRequired,
460
461         [System.Boolean]
462         $PasswordChangeNotAllowed
463     )
464
465     Set-StrictMode -Version Latest
466
467     ValidateUserName -UserName $UserName
468
469     # Try to find a user by a name.
470     $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine)
471
472     try
473     {
474         $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName);
475         if($user -eq $null)
476         {
477             # A user with the provided name does not exist.
478             Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName)
479
480             if($Ensure -eq "Absent")
481             {
482                 return $true;
483             }
484             else
485             {
486                 return $false;
487             }
488         }
489
490         # A user with the provided name exists.
491         Write-Log -Message ($LocalizedData.UserExists -f $UserName)
492
493         # Validate separate properties.
494         if($Ensure -eq "Absent")
495         {
496             Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present")
497             return $false; # The Ensure property does not match. Return $false;
498         }
499
500         if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.DisplayName)
501         {
502             Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.DisplayName)
503             return $false; # The FullName property does not match. Return $false;
504         }
505
506         if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description)
507         {
508             Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description)
509             return $false; # The Description property does not match. Return $false;
510         }
511
512         # Password
513         if($PSBoundParameters.ContainsKey('Password'))
514         {
515             if(-not $principalContext.ValidateCredentials($UserName, $Password.GetNetworkCredential().Password))
516             {
517                 Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password")
518                 return $false; # The Password property does not match. Return $false;
519             }
520         }
521
522         if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled)
523         {
524             Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled)
525             return $false; # The Disabled property does not match. Return $false;
526         }
527
528         if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $user.PasswordNeverExpires)
529         {
530             Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $user.PasswordNeverExpires)
531             return $false; # The PasswordNeverExpires property does not match. Return $false;
532         }
533
534         if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword)
535         {
536             Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, $user.UserCannotChangePassword)
537             return $false; # The PasswordChangeNotAllowed property does not match. Return $false;
538         }
539     }
540     catch
541     {
542          ThrowExceptionDueToDirectoryServicesError -ErrorId "ConnectionError" -ErrorMessage ($LocalizedData.ConnectionError + $_)
543     }
544         
545     finally
546     {
547         if($user -ne $null)
548         {
549             $user.Dispose();
550         }
551
552         $principalContext.Dispose();
553        
554     }
555
556     # All properties match. Return $true.
557     Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName)
558     return $true;
559 }
560
561
562 <#
563 .Synopsys
564 The Get-TargetResource cmdlet.
565 #>
566 function Get-TargetResourceOnNanoServer
567 {
568     param
569     (
570         [parameter(Mandatory = $true)]
571         [ValidateNotNullOrEmpty()]
572         [System.String]
573         $UserName
574     )
575
576     Set-StrictMode -Version Latest
577
578     ValidateUserName -UserName $UserName
579
580     # Try to find a user by a name.
581     try
582     {
583         [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
584     }
585     catch [System.Exception]
586     {
587         if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
588         {
589             # The user is not found. Return Ensure=Absent.
590             return @{
591                         UserName = $UserName; 
592                         Ensure = "Absent";
593                     }
594         }
595         Throw-TerminatingError -ErrorRecord $_
596     }
597
598     # The user is found. Return all user properties and Ensure="Present".
599     $returnValue = @{
600                         UserName = $user.Name; 
601                         Ensure = "Present";
602                         FullName = $user.FullName;
603                         Description = $user.Description;
604                         Disabled = -not $user.Enabled;
605                         PasswordChangeRequired = $null;
606                         PasswordChangeNotAllowed = -not $user.UserMayChangePassword;
607                     }
608
609     if ($user.PasswordExpires)
610     {
611         $returnValue.Add('PasswordNeverExpires', $false)
612     }
613     else
614     {
615         $returnValue.Add('PasswordNeverExpires', $true)
616     }
617
618     return $returnValue;
619 }
620
621 <#
622 .Synopsys
623 The Set-TargetResource cmdlet.
624 #>
625 function Set-TargetResourceOnNanoServer
626 {
627     [CmdletBInding(SupportsShouldProcess=$true)]
628     param
629     (
630         [parameter(Mandatory = $true)]
631         [ValidateNotNullOrEmpty()]
632         [System.String]
633         $UserName,
634
635         [ValidateSet("Present", "Absent")]
636         [System.String]
637         $Ensure = "Present",
638
639         [System.String]
640         $FullName,
641
642         [System.String]
643         $Description,
644
645         [ValidateNotNullOrEmpty()]
646         [System.Management.Automation.PSCredential]
647         $Password,
648
649         [System.Boolean]
650         $Disabled,
651
652         [System.Boolean]
653         $PasswordNeverExpires,
654
655         [System.Boolean]
656         $PasswordChangeRequired,
657
658         [System.Boolean]
659         $PasswordChangeNotAllowed
660     )
661
662     Set-StrictMode -Version Latest
663
664     Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName)
665
666     ValidateUserName -UserName $UserName
667
668     ## Try to find a user by a name.
669     [bool] $userExists = $false
670     try
671     {
672         [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
673         $userExists = $true;
674     }
675     catch [System.Exception]
676     {
677         if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
678         {
679             # The user is not found.
680             Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName)
681         }
682         else
683         {
684             Throw-TerminatingError -ErrorRecord $_
685         }
686     }
687
688     if($Ensure -eq "Present")
689     {
690         # Ensure is set to "Present".
691     
692         if(-not $userExists)
693         {
694             # The user with the provided name does not exist. Add a new user.
695             New-LocalUser -Name $UserName -NoPassword
696             Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName)
697         }
698     
699         # Set user properties.
700         if($PSBoundParameters.ContainsKey('FullName'))
701         {
702             if (-not $userExists -or $FullName -ne $user.FullName)
703             {
704                 if ($FullName -eq $null)
705                 {
706                     Set-LocalUser -Name $UserName -FullName ([String]::Empty)
707                 }
708                 else
709                 {
710                     Set-LocalUser -Name $UserName -FullName $FullName
711                 }
712             }
713         }
714         else
715         {
716             if (-not $userExists)
717             {
718                 # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name.
719                 Set-LocalUser -Name $UserName -FullName ([String]::Empty)
720             }
721         }
722     
723         if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description))
724         {
725             if ($Description -eq $null)
726             {
727                 Set-LocalUser -Name $UserName -Description ([String]::Empty)
728             }
729             else
730             {
731                 Set-LocalUser -Name $UserName -Description $Description
732             }
733         }
734     
735         # Password. Set the password regardless of the state of the user.
736         if($PSBoundParameters.ContainsKey('Password'))
737         {
738             Set-LocalUser -Name $UserName -Password $Password.Password
739         }
740     
741         if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled))
742         {
743             if ($Disabled)
744             {
745                 Disable-LocalUser -Name $UserName
746             }
747             else
748             {
749                 Enable-LocalUser -Name $UserName
750             }
751         }
752     
753         $existingUserPasswordNeverExpires = (($userExists) -and ($user.PasswordExpires -eq $null))
754         if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or ($PasswordNeverExpires -ne $existingUserPasswordNeverExpires)))
755         {
756             Set-LocalUser -Name $UserName -PasswordNeverExpires:$passwordNeverExpires
757         }
758     
759         # NOTE: The parameter name and the property name have opposite meaning.
760         [bool] $expected = -not $PasswordChangeNotAllowed
761         [bool] $actual = $expected
762         if($userExists) {
763             $actual = $user.UserMayChangePassword
764         }
765         if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $expected -ne $actual))
766         {
767             Set-LocalUser -Name $UserName -UserMayChangePassword $expected
768         }
769     }
770     else
771     {
772         # Ensure is set to "Absent".
773         if($userExists)
774         {
775             # The user exists.
776             Remove-LocalUser -Name $UserName
777     
778             Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName)
779         }
780         else
781         {
782             Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName)
783         }
784     }
785
786
787     Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName)
788 }
789
790 <#
791 .Synopsys
792 The Test-TargetResource cmdlet is used to validate if the resource is in a state as expected in the instance document.
793 #>
794 function Test-TargetResourceOnNanoServer
795 {
796     param
797     (
798         [parameter(Mandatory = $true)]
799         [ValidateNotNullOrEmpty()]
800         [System.String]
801         $UserName,
802
803         [ValidateSet("Present", "Absent")]
804         [System.String]
805         $Ensure = "Present",
806
807         [System.String]
808         $FullName,
809
810         [System.String]
811         $Description,
812
813         [ValidateNotNullOrEmpty()]
814         [System.Management.Automation.PSCredential]
815         $Password,
816
817         [System.Boolean]
818         $Disabled,
819
820         [System.Boolean]
821         $PasswordNeverExpires,
822
823         [System.Boolean]
824         $PasswordChangeRequired,
825
826         [System.Boolean]
827         $PasswordChangeNotAllowed
828     )
829
830     Set-StrictMode -Version Latest
831
832     ValidateUserName -UserName $UserName
833
834     # Try to find a user by a name.
835     try
836     {
837         [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop
838     }
839     catch [System.Exception]
840     {
841         if ($_.CategoryInfo.ToString().Contains('UserNotFoundException'))
842         {
843             # The user is not found. Return Ensure=Absent.
844             if($Ensure -eq "Absent")
845             {
846                 return $true
847             }
848             else
849             {
850                 return $false
851             }
852         }
853         Throw-TerminatingError -ErrorRecord $_
854     }
855
856     # A user with the provided name exists.
857     Write-Log -Message ($LocalizedData.UserExists -f $UserName)
858     
859     # Validate separate properties.
860     if($Ensure -eq "Absent")
861     {
862         Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present")
863         return $false; # The Ensure property does not match. Return $false;
864     }
865     
866     if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.FullName)
867     {
868         Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.FullName)
869         return $false; # The FullName property does not match. Return $false;
870     }
871     
872     if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description)
873     {
874         Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description)
875         return $false; # The Description property does not match. Return $false;
876     }
877     
878     if($PSBoundParameters.ContainsKey('Password'))
879     {
880         if(-not (ValidateCredentialsOnNanoServer -UserName $UserName -Password $Password.Password))
881         {
882             Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password")
883             return $false; # The Password property does not match. Return $false;
884         }
885     }
886     
887     if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled)
888     {
889         Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled)
890         return $false; # The Disabled property does not match. Return $false;
891     }
892     
893     $existingUserPasswordNeverExpires = ($user.PasswordExpires -eq $null)
894     if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $existingUserPasswordNeverExpires)
895     {
896         Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $existingUserPasswordNeverExpires)
897         return $false; # The PasswordNeverExpires property does not match. Return $false;
898     }
899     
900     if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne (-not $user.UserMayChangePassword))
901     {
902         Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, (-not $user.UserMayChangePassword))
903         return $false; # The PasswordChangeNotAllowed property does not match. Return $false;
904     }
905
906     # All properties match. Return $true.
907     Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName)
908     return $true;
909 }
910
911 <#
912 .Synopsis
913 Validates the User name for invalid charecters.
914 #>
915 function ValidateUserName
916 {
917     param
918     (
919         [parameter(Mandatory = $true)]
920         [ValidateNotNullOrEmpty()]
921         [System.String]
922         $UserName
923     )
924
925     # Check if the name consists of only periods and/or white spaces.
926     $wrongName = $true;
927     for($i = 0; $i -lt $UserName.Length; $i++)
928     {
929         if(-not [Char]::IsWhiteSpace($UserName, $i) -and $UserName[$i] -ne '.')
930         {
931             $wrongName = $false;
932             break;
933         }
934     }
935
936     $invalidChars = @('\','/','"','[',']',':','|','<','>','+','=',';',',','?','*','@')
937
938     if($wrongName)
939     {
940         ThrowInvalidArgumentError -ErrorId "UserNameHasOnlyWhiteSpacesAndDots" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars))
941     }
942
943     if($UserName.IndexOfAny($invalidChars) -ne -1)
944     {
945         ThrowInvalidArgumentError -ErrorId "UserNameHasInvalidCharachter" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars))
946     }
947 }
948
949 <#
950 .Synopsis
951 Throws an argument error.
952 #>
953 function ThrowInvalidArgumentError
954 {
955     [CmdletBinding()]
956     param
957     (
958         
959         [parameter(Mandatory = $true)]
960         [ValidateNotNullOrEmpty()]
961         [System.String]
962         $ErrorId,
963
964         [parameter(Mandatory = $true)]
965         [ValidateNotNullOrEmpty()]
966         [System.String]
967         $ErrorMessage
968     )
969
970     $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument
971     $exception = New-Object System.ArgumentException $ErrorMessage;
972     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null
973     throw $errorRecord
974 }
975
976 function ThrowExceptionDueToDirectoryServicesError
977 {
978     [CmdletBinding()]
979     param
980     (
981         [parameter(Mandatory = $true)]
982         [ValidateNotNullOrEmpty()]
983         [System.String]
984         $ErrorId,
985
986         [parameter(Mandatory = $true)]
987         [ValidateNotNullOrEmpty()]
988         [System.String]
989         $ErrorMessage
990     )
991
992     $errorCategory = [System.Management.Automation.ErrorCategory]::ConnectionError
993     $exception = New-Object System.ArgumentException $ErrorMessage
994     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null
995     throw $errorRecord
996 }
997
998 Function Throw-TerminatingError
999 {
1000     param(
1001         [string] $Message,
1002         [System.Management.Automation.ErrorRecord] $ErrorRecord
1003     )
1004     
1005
1006     if ($ErrorRecord -ne $null)
1007     {
1008         $exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception
1009     }
1010     else
1011     {
1012         $exception = new-object "System.InvalidOperationException" $Message
1013     }
1014     $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null
1015     throw $errorRecord
1016 }
1017
1018 <#
1019 .Synopsis
1020 Writes either to Verbose or ShouldProcess channel.
1021 #>
1022 function Write-Log
1023 {
1024     [CmdletBinding(SupportsShouldProcess=$true)]
1025     param
1026     (    
1027         [parameter(Mandatory = $true)]
1028         [ValidateNotNullOrEmpty()]
1029         [System.String]
1030         $Message
1031     )
1032
1033     if ($PSCmdlet.ShouldProcess($Message, $null, $null))
1034     {
1035         Write-Verbose $Message        
1036     }    
1037 }
1038
1039 <#
1040 .Synopsis
1041 Validates the local user's credentials on the local machine.
1042 #>
1043 Function ValidateCredentialsOnNanoServer
1044 {
1045     param
1046     (
1047         [parameter(Mandatory = $true)]
1048         [ValidateNotNullOrEmpty()]
1049         [System.String]
1050         $UserName,
1051
1052         [ValidateNotNullOrEmpty()]
1053         [securestring]
1054         $Password
1055     )
1056
1057     $source = @'
1058         [Flags]
1059         private enum LogonType
1060         {
1061             Logon32LogonInteractive = 2,
1062             Logon32LogonNetwork,
1063             Logon32LogonBatch,
1064             Logon32LogonService,
1065             Logon32LogonUnlock,
1066             Logon32LogonNetworkCleartext,
1067             Logon32LogonNewCredentials
1068         }
1069         
1070         [Flags]
1071         private enum LogonProvider
1072         {
1073             Logon32ProviderDefault = 0,
1074             Logon32ProviderWinnt35,
1075             Logon32ProviderWinnt40,
1076             Logon32ProviderWinnt50
1077         }
1078
1079         [DllImport("api-ms-win-security-logon-l1-1-1.dll", CharSet = CharSet.Unicode, SetLastError = true)]
1080         private static extern Boolean LogonUser(
1081             String lpszUserName,
1082             String lpszDomain,
1083             IntPtr lpszPassword,
1084             LogonType dwLogonType,
1085             LogonProvider dwLogonProvider,
1086             out IntPtr phToken
1087             );
1088
1089         
1090         [DllImport("api-ms-win-core-handle-l1-1-0.dll",
1091             EntryPoint = "CloseHandle", SetLastError = true,
1092             CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
1093         internal static extern bool CloseHandle(IntPtr handle);
1094
1095         public static bool ValidateCredentials(string username, SecureString password)
1096         {
1097             IntPtr tokenHandle = IntPtr.Zero;
1098             IntPtr unmanagedPassword = IntPtr.Zero;
1099             
1100             unmanagedPassword = SecureStringMarshal.SecureStringToCoTaskMemUnicode(password);
1101
1102             try 
1103             {
1104                 return LogonUser(
1105                     username,
1106                     null,
1107                     unmanagedPassword,
1108                     LogonType.Logon32LogonInteractive,
1109                     LogonProvider.Logon32ProviderDefault,
1110                     out tokenHandle);
1111             }
1112             catch
1113             {
1114                 return false;
1115             }
1116             finally
1117             {
1118                 if (tokenHandle != IntPtr.Zero)
1119                 {
1120                     CloseHandle(tokenHandle);
1121                 }
1122                 if (unmanagedPassword != IntPtr.Zero) {
1123                     Marshal.ZeroFreeCoTaskMemUnicode(unmanagedPassword);
1124                 }
1125                 unmanagedPassword = IntPtr.Zero;
1126             }
1127         }
1128 '@
1129     
1130     Add-Type -PassThru -Namespace Microsoft.Windows.DesiredStateConfiguration.NanoServer.UserResource `
1131         -Name CredentialsValidationTool -MemberDefinition $source -Using System.Security -ReferencedAssemblies System.Security.SecureString.dll | Out-Null
1132     return [Microsoft.Windows.DesiredStateConfiguration.NanoServer.UserResource.CredentialsValidationTool]::ValidateCredentials($UserName, $Password) 
1133 }
1134
1135
1136 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource