xModules not downloading from PullServer

This topic contains 15 replies, has 3 voices, and was last updated by Profile photo of Ryan Young Ryan Young 2 years ago.

  • Author
    Posts
  • #20775
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Hello once again! I have my configuration working for anything that is "built in", but I'm trying to employ a configuration with actions from the module xSmbShare and I'm getting this powershell error when I try to force execute the pull action on a target node:

     
    [dyul1sql003]: PS C:\Users\ryoung\Documents> $params = @{
        Namespace  = 'root/Microsoft/Windows/DesiredStateConfiguration'
        ClassName  = 'MSFT_DSCLocalConfigurationManager'
        MethodName = 'PerformRequiredConfigurationChecks'
        Arguments  = @{
            Flags = [uint32] 1
        }
    }
    
    Invoke-CimMethod @params
    Invoke-CimMethod : Cannot find module xSmbShare.1.0 from the server http://dyul1dsc001.cie.caesarsint.com:8080/PSDSCPullServer.svc/Module(ConfigurationId='4db79333-44e1
    -4e75-8c30-8fedb0b10f11',ModuleName='xSmbShare',ModuleVersion='1.0')/ModuleContent.
    At line:10 char:1
    + Invoke-CimMethod @params
    + ~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : ResourceUnavailable: (root/Microsoft/...gurationManager:String) [Invoke-CimMethod], CimException
        + FullyQualifiedErrorId : WebDownloadManagerModuleNotFound,Microsoft.PowerShell.DesiredStateConfiguration.Commands.GetDscModuleCommand,Microsoft.Management.Infrast 
       ructure.CimCmdlets.InvokeCimMethodCommand
    

    It looks like it's trying to find a folder with the configurationid under the Modules folder. When I configured my pull server, I used the below configuration:

    configuration PullServer
    {
    param
    (
    [string[]]$ComputerName = 'localhost'
    )
    
        Import-DSCResource -ModuleName xPSDesiredStateConfiguration
    
        Node $ComputerName
    {
    WindowsFeature DSCServiceFeature
    {
    Ensure = “Present”
    Name   = “DSC-Service”
    }
    
            xDscWebService PSDSCPullServer
    {
    Ensure                  = “Present”
    EndpointName            = “PSDSCPullServer”
    Port                    = 8080
    PhysicalPath            = “$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer”
    CertificateThumbPrint   = “AllowUnencryptedTraffic”
    ModulePath              = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules”
    ConfigurationPath       = “$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration”
    State                   = “Started”
    DependsOn               = “[WindowsFeature]DSCServiceFeature”
    }
    
            xDscWebService PSDSCComplianceServer
    {
    Ensure                  = “Present”
    EndpointName            = “PSDSCComplianceServer”
    Port                    = 9080
    PhysicalPath            = “$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer”
    CertificateThumbPrint   = “AllowUnencryptedTraffic”
    State                   = “Started”
    IsComplianceServer      = $true
    DependsOn               = (“[WindowsFeature]DSCServiceFeature”,”[xDSCWebService]PSDSCPullServer”)
    }
    }
    } 

    I can attest that I've put the modules under C:\Program Files\WindowsPowerShell\DscService\Modules, I have zipped them and added a checksum.

    Any clues/ideas?

  • #20776
    Profile photo of Daniel Krebs
    Daniel Krebs
    Participant

    Ryan,

    The pull server will be looking for a ZIP file named xSmbShare_1.0.zip and matching checksum file. How did you name the ZIP file?

  • #20777
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Thanks Daniel, I missed the detail that it wants the filename to include module version. I've fixed that and am now faced with this:

    Invoke-CimMethod @params
    Invoke-CimMethod : Failed to extract the module from zip file C:\Windows\TEMP\\635523491861734499\xSmbShare_1.0.zip downloaded by Download Manager WebDownloadManager.
    At line:10 char:1
    + Invoke-CimMethod @params
    + ~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidResult: (root/Microsoft/...gurationManager:String) [Invoke-CimMethod], CimException
        + FullyQualifiedErrorId : ModuleExtractionFailed,Microsoft.Management.Infrastructure.CimCmdlets.InvokeCimMethodCommand
    

    I'm thinking this is cause I zipped it originally with 7 zip instead of the built in zip. Will have a go at it and report back.

  • #20779
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    The current version of DSC is pretty finicky about how the zip file is created. This is one of the things they're addressing in WMF 5.0.

  • #20799
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Indeed this is what I'm coming to notice. I'm trying to package an easy "double click this script to unzip the resource wave kit, package it for DSC with zips and checksums, and move it to the right spot" so that I have an easy "howto deploy a new pull server" guide, and it's not working cause of this limitation. What do you use to zip the 20+ modules in the resource kit? Or am I forced to do this manually?

  • #20800
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    There's a PowerShell function for this in the DscBuild module: https://github.com/PowerShellOrg/DSC/blob/master/Tooling/dscbuild/New-DscZipFile.ps1 . It has a dependency on another function in the same module for determining the module version number, but you don't have to use it as-is if you don't want to. The important bits are creating a new zip file with the correct header, then using the Shell.Application COM object to add content to the zip file.

  • #20803
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Awesome 🙂 Thanks yet again Dave!

  • #20850
    Profile photo of Ryan Young
    Ryan Young
    Participant

    In Steve's script he's using a function Get-ModuleVersion. It has a paramater that requires a $path. If I enter a full path it works. but it won't seem to accept me using resolve-path along with get-childitem so that I can recursively do this for all the modules. Steve's script below:

    function Get-ModuleVersion
    {
        param (
            [parameter(mandatory)]
            [validatenotnullorempty()]
            [string]
            $path, 
            [switch]
            $asVersion
        )
        $ModuleName = split-path $path -Leaf
        $ModulePSD1 = join-path $path "$ModuleName.psd1"
        
        Write-Verbose ''
        Write-Verbose "Checking for $ModulePSD1"
        if (Test-Path $ModulePSD1)
        {
            $psd1 = get-content $ModulePSD1 -Raw        
            $Version = (Invoke-Expression -Command $psd1)['ModuleVersion']
            Write-Verbose "Found version $Version for $ModuleName."
            Write-Verbose ''
            if ($asVersion) {
                [Version]::parse($Version)
            }
            else {            
                return $Version
            }   
        }
        else
        {
            Write-Warning "Could not find a PSD1 for $modulename at $ModulePSD1."        
        }
    }
  • #20851
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    If you're having a problem using that function, please show the calling code along with whatever errors you're receiving.

  • #20852
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Not sure what you're referring to with calling code :). Haven't gotten there. I'm really doing things quite split/seperately. I went and downloaded/copied steve's new-dsczipfile script and noticed within it that it was looking for info on "module version" so I found the module version script, and I'm trying to run it on the folder I'm currently looking at in ISE so on '.\'. I'm not calling it from another script, I just want to have this be a seperate / manual process for now.

    function Get-ModuleVersion
    {
        param (
            [parameter(mandatory)]
            [validatenotnullorempty()]
            [string]
            $path, 
            [switch]
            $asVersion
        )
        $ModuleName = split-path $path -Leaf
        $ModulePSD1 = join-path $path "$ModuleName.psd1"
        
        Write-Verbose ''
        Write-Verbose "Checking for $ModulePSD1"
        if (Test-Path $ModulePSD1)
        {
            $psd1 = get-content $ModulePSD1 -Raw        
            $Version = (Invoke-Expression -Command $psd1)['ModuleVersion']
            Write-Verbose "Found version $Version for $ModuleName."
            Write-Verbose ''
            if ($asVersion) {
                [Version]::parse($Version)
            }
            else {            
                return $Version
            }   
        }
        else
        {
            Write-Warning "Could not find a PSD1 for $modulename at $ModulePSD1."        
        }
    }
        
    
    function Get-ModuleAuthor
    {
        param (
            [parameter(mandatory)]
            [validatenotnullorempty()]        
            [string]
            $path
        )
        $ModuleName = split-path $path -Leaf
        $ModulePSD1 = join-path $path "$ModuleName.psd1"
        
        if (Test-Path $ModulePSD1)
        {
            $psd1 = get-content $ModulePSD1 -Raw        
            $Author = (Invoke-Expression -Command $psd1)['Author']
            Write-Verbose "Found author $Author for $ModuleName."
            return $Author
        }
        else
        {
            Write-Warning "Could not find a PSD1 for $modulename at $ModulePSD1."
        }    
    }
    
    New-Alias -Name Get-DscResourceVersion -Value Get-ModuleVersion -Force
    
    
    function Test-ModuleVersion {
        param (
            [parameter(ValueFromPipeline, Mandatory)]
            [object]
            $InputObject, 
            [parameter(Mandatory, position = 0)]
            [string]
            $Destination
        )
        process {
            $DestinationModule = join-path $Destination $InputObject.name
    
            if (test-path $DestinationModule) {
                $CurrentModuleVersion = Get-ModuleVersion -Path $DestinationModule -asVersion
                $NewModuleVersion = Get-ModuleVersion -Path $InputObject.fullname -asVersion
                if (($CurrentModuleVersion -eq $null) -or ($NewModuleVersion -gt $CurrentModuleVersion)) {
                    Write-Verbose "New module version is higher the the currently deployed module.  Replacing it."
                    $InputObject
                }
                else {
                    Write-Verbose "The current module is the same version or newer than the one in source control.  Not replacing it."
                }
            }
            else {
                Write-Verbose "No existing module at $DestinationModule."
                $InputObject
            }
        }
    
    }
    
    function New-DscZipFile
    {
        param(
            ## The name of the zip archive to create
            [parameter(ValueFromPipelineByPropertyName)]
            [alias('Name')]
            [string]
            $ZipFile,
    
            ## The name of the folder to archive
            [parameter(ValueFromPipelineByPropertyName)]
            [alias('FullName')]
            [string]
            $Path,
    
            ## Switch to delete the zip archive if it already exists.
            [Switch] $Force
        )
    
        
        begin
        {
            [Byte[]] $zipHeader = 0x50,0x4B,0x05,0x06,0x00,0x00,0x00,0x00,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
                                    0x00,0x00,0x00,0x00,0x00,0x00
        }
        process
        {
            ## Create the Zip File
            $Version = Get-DscResourceVersion $path
            $folderName = $ZipFile + "_"+ $Version
                    
            $ZipName = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$folderName.zip")
            Write-Verbose "Packing $path to to $ZipName."
    
            ## Check if the file exists already. If it does, check
            ## for -Force - generate an error if not specified.
            if(Test-Path $zipName)
            {
                if($Force)
                {
                    Write-Verbose "Removing previous $zipname"
                    Remove-Item $zipName -Force
                }
                else
                {
                    throw "Item with specified name $zipName already exists."
                }
            }
    
            try
            {
                $shellObject = New-Object -comobject "Shell.Application"
    
                Write-Verbose "Creating new zip file $ZipName."
                $Writer = New-Object System.IO.FileStream $ZipName, "Create"
                $Writer.Write($zipheader, 0, 22)
                $Writer.Close();
    
                Start-Sleep -Seconds 1
                $ZipFileObject = $shellObject.namespace($ZipName)
    
                Write-Verbose "Loading the zip file contents."
                $ZipFileObject.CopyHere($Path)
                Start-Sleep -Seconds 5
            }
            finally
            {            
                ## Release the shell object
                $shellObject = $null
                $ZipFileObject = $null
            }
            get-item $ZipName
        }
    }

    And the error:

    PS C:\DSCCustom\Workdir\All Resources> New-DscZipFile -Path .\
    WARNING: Could not find a PSD1 for All Resources at .\All Resources.psd1.
    Item with specified name C:\DSCCustom\Workdir\All Resources\_.zip already exists.
    At line:140 char:17
    +                 throw "Item with specified name $zipName already exists."
    +                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OperationStopped: (Item with speci...already exists.:String) [], RuntimeException
        + FullyQualifiedErrorId : Item with specified name C:\DSCCustom\Workdir\All Resources\_.zip already exists.
     
    
    PS C:\DSCCustom\Workdir\All Resources> New-DscZipFile -Path .\
    WARNING: Could not find a PSD1 for All Resources at .\All Resources.psd1.
  • #20853
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    It looks like the Path parameter of New-DscZipFile should be the path to the root folder of a PowerShell module (and that module must contain a psd1 file – module manifest – for the function to work.)

    What's in your "All Resources" folder?

  • #20854
    Profile photo of Ryan Young
    Ryan Young
    Participant

    an unzipped dump of all the modules from the latest resource kit. Here's the 'goal'

    Tech who wants to update resource modules goes to c:\DSCcustom\production resources. Pastes the latest resource kit from the net, and unzips it (and from what I've seen, when unzipped it will always unzip as "All resources".
    have a script in a location: c:\DSCcustom\production resources\
    This script, when run, will find a folder "all resources" and recursively create zip files for all of the modules there with appropriate names for DSC pull.
    Either by script or manual tech intervention, the files somehow find themselves in the right folder for dsc pull server with checksums 🙂 I can probably script this myself at this point, but the actual act of zipping everything by script is eluding me!

  • #20856
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I've only used this function by way of the Invoke-DscBuild command, which is already set up to use it properly. Based on some quick testing, here's what you need to do (assuming you don't want to modify the code in some way to change this behavior):

    Change your current working directory to the location where you want the zip files to be created, then run:

    Get-ChildItem -Path 'C:\DSCCustom\Workdir\All Resources' -Directory |
    New-DscZipFile
    

    New-DscZipFile was written with having Get-Item or Get-ChildItem (DirectoryInfo objects) as pipeline input, so you don't need to do anything else. However, you're still going to have to be careful about just mindlessly unzipping the resource kit from Microsoft and expecting this to work. For example, xExchange has an extra level of folder nesting for whatever reason (All Resources\xExchange\xExchange\xExchange.psd1), which causes this code to fail for that particular module.

  • #20857
    Profile photo of Ryan Young
    Ryan Young
    Participant

    Exactly what I was looking for, thanks Dave! Hmm good to know for the exchange module, but in our particular case we won't be managing exchange with DSC in the forseen future.

    I should download the DSC ebook and read more about the invoke-dscbuild. Probably my lack of understanding/knowledge behind this section of DSC is what's holding me back from advancing quickly. DSC is very much pushing the limits of my knowledge and I'm learning something everyday, but applying that knowledge is still difficult for me :).

  • #20858
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I don't think the eBook has anything about Invoke-DscBuild in it, but I could be wrong. Invoke-DscBuild is part of the tooling modules that Steve Murawski wrote for StackExchange, and later posted to powershell.org's GitHub repository. (In other words, it's a community thing, not from Microsoft.)

  • #20859
    Profile photo of Ryan Young
    Ryan Young
    Participant

    aye, I haven't looked at the book yet and intend to soon so I just kind of assumed it was there. I hope to spend some time writing up a reddit wiki/post/article about "DSC for script kiddie sysadmins" who, like me are used to group policy and our comfy active directory environments. I'd very much like to have a guide aimed at people who either don't care how it works or have little time to learn how it works, they just need it to work and learn about it afterwards. To that end I am thinking I will create a zip file with my configs which can easily be hacked for any AD environment, and let people download it for their test labs/personal use.

    A lot of what Steve has done is beyond my current capacity/knowledge/comfort zone in powershell. It's very advanced and extremely cool, but not simple for the run of the mill sysadmin. Dsc from what I can see is a tool that a lot of 'run of the mill' sysadmins would use if it was cookie-cutter and 'simple'. I think giving them a mostly completed config with a "DSC for everyday sysadmins" guide could propel the use of DSC.

    Anyway there's my typical rant. Problem is indeed solved, Happy american turkey day weekend! (I'm Canadian so turkey day was about a month ago for us!)

You must be logged in to reply to this topic.