Functions/Start-DscPullConfiguration.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Start-DscPullConfiguration
{
    <#
    .SYNOPSIS
    Performs a configuration check on a computer that is using DSC's Pull refresh mode.
 
    .DESCRIPTION
    The most frequently a computer's LCM will download new configuration is every 15 minutes; the most frequently it will apply it is every 30 minutes. This function contacts a computer's LCM and tells it to apply and download its configuration immediately.
 
    If a computer's LCM isn't configured to pull its configuration, an error is written, and nothing happens.
 
    If a configuration check fails, the errors are retrieved from the computer's event log and written out as errors. The `Remote Event Log Management` firewall rules must be enabled on the computer for this to work. If they aren't, you'll see an error explaining this. The `Get-DscError` help topic shows how to enable these firewall rules.
 
    Sometimes, the LCM does a really crappy job of updating to the latest version of a module. `Start-DscPullConfiguration` will delete modules on the target computers. Specify the names of the modules to delete with the `ModuleName` parameter. Make sure you only delete modules that will get installed by the LCM. Only modules installed in the `$env:ProgramFiles\WindowsPowerShell\Modules` directory are removed.
 
    `Start-DscPullConfiguration` is new in Carbon 2.0.
 
    .LINK
    Get-DscError
 
    .LINK
    Initialize-Lcm
 
    .LINK
    Get-DscWinEvent
 
    .EXAMPLE
    Start-DscPullConfiguration -ComputerName '10.1.2.3','10.4.5.6'
 
    Demonstrates how to immedately download and apply a computer from its pull server.
 
    .EXAMPLE
    Start-DscPullConfiguration -ComputerName '10.1.2.3' -Credential (Get-Credential domain\username)
 
    Demonstrates how to use custom credentials to contact the remote server.
 
    .EXAMPLE
    Start-DscPullConfiguration -CimSession $session
 
    Demonstrates how to use one or more CIM sessions to invoke a configuration check.
 
    .EXAMPLE
    Start-DScPullConfiguration -ComputerName 'example.com' -ModuleName 'Carbon'
 
    Demonstrates how to delete modules on the target computers, because sometimes the LCM does a really crappy job of it.
    #>

    [CmdletBinding(DefaultParameterSetName='WithCredentials')]
    param(
        [Parameter(Mandatory=$true,ParameterSetName='WithCredentials')]
        [string[]]
        # The credential to use when connecting to the target computer.
        $ComputerName,

        [Parameter(ParameterSetName='WithCredentials')]
        [PSCredential]
        # The credentials to use when connecting to the computers.
        $Credential,

        [Parameter(ParameterSetName='WithCimSession')]
        [Microsoft.Management.Infrastructure.CimSession[]]
        $CimSession,

        [string[]]
        # Any modules that should be removed from the target computer's PSModulePath (since the LCM does a *really* crappy job of removing them).
        $ModuleName
    )

    Set-StrictMode -Version 'Latest'

    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $credentialParam = @{ }
    if( $PSCmdlet.ParameterSetName -eq 'WithCredentials' )
    {
        if( $Credential )
        {
            $credentialParam.Credential = $Credential
        }

        $CimSession = New-CimSession -ComputerName $ComputerName @credentialParam
        if( -not $CimSession )
        {
            return
        }
    }

    $CimSession = Get-DscLocalConfigurationManager -CimSession $CimSession |
                    ForEach-Object {
                        if( $_.RefreshMode -ne 'Pull' )
                        {
                            Write-Error ('The Local Configuration Manager on ''{0}'' is not in Pull mode (current RefreshMode is ''{1}'').' -f $_.PSComputerName,$_.RefreshMode)
                            return
                        }

                        foreach( $session in $CimSession )
                        {
                            if( $session.ComputerName -eq $_.PSComputerName )
                            {
                                return $session
                            }
                        }
                    }

    if( -not $CimSession )
    {
        return
    }

    # Get rid of any _tmp directories you might find out there.
    Invoke-Command -ComputerName $CimSession.ComputerName @credentialParam -ScriptBlock {
        $modulesRoot = Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell\Modules'
        Get-ChildItem -Path $modulesRoot -Filter '*_tmp' -Directory | 
            Remove-Item -Recurse
    }

    if( $ModuleName )
    {
        # Now, get rid of any modules we know will need to get updated
        Invoke-Command -ComputerName $CimSession.ComputerName @credentialParam -ScriptBlock {
            param(
                [string[]]
                $ModuleName
            )

            $dscProcessID = Get-WmiObject msft_providers | 
                                Where-Object {$_.provider -like 'dsccore'} | 
                                Select-Object -ExpandProperty HostProcessIdentifier 
            Stop-Process -Id $dscProcessID -Force

            $modulesRoot = Join-Path -Path $env:ProgramFiles -ChildPath 'WindowsPowerShell\Modules'
            Get-ChildItem -Path $modulesRoot -Directory |
                Where-Object { $ModuleName -contains $_.Name } |
                Remove-Item -Recurse

        } -ArgumentList (,$ModuleName)
    }

    # Getting the date/time on the remote computers so we can get errors later.
    $win32OS = Get-CimInstance -CimSession $CimSession -ClassName 'Win32_OperatingSystem'

    $results = Invoke-CimMethod -CimSession $CimSession `
                                -Namespace 'root/microsoft/windows/desiredstateconfiguration' `
                                -Class 'MSFT_DscLocalConfigurationManager' `
                                -MethodName 'PerformRequiredConfigurationChecks' `
                                -Arguments @{ 'Flags' = [uint32]1 } 

    $successfulComputers = $results | Where-Object { $_ -and $_.ReturnValue -eq 0 } | Select-Object -ExpandProperty 'PSComputerName'

    $CimSession | 
        Where-Object { $successfulComputers -notcontains $_.ComputerName } |
        ForEach-Object { 
            $session = $_
            $startedAt= $win32OS | Where-Object { $_.PSComputerName -eq $session.ComputerName } | Select-Object -ExpandProperty 'LocalDateTime'
            Get-DscError -ComputerName $session.ComputerName -StartTime $startedAt -Wait 
        } | 
        Write-DscError
}