• 5 PowerShell functions to create a SCCM “trickle install” – i.e. X at a time.

    If you look at this post, I describe a my idea of a SCCM trickle install – that is X number of machines at a time. Rather that have the install hit the whole collection at once, I wanted to hit just 20 a night. That way if there is an error, we can put the breaks on the deployment. We create dynamic collections that use a “NOT IN” SQL statement – this results in all the machines that are in the collection need the specific software. This script copies X at a time from that collection to new collection.

    In the previous post, I run a VBScript that loops through the first 10 (or X) number of machines in the collection and add them to a new collection that contains the assigned advertisement. The next night, the script runs again, and the next 10 (or X) are copied (since the source collection is dynamic, the machines from the night before are removed).

    I wanted to do the same thing, but I wanted to use PowerShell. First up, a function to find a Collection’s Id.

    FUNCTION JBM-SCCM-GetCollection {
    	Param([parameter(Mandatory = $true)]$CollectionName,
    	$SiteName="JBM",
    	$SCCMServer="sccmserver.domain.com")
    	$SCCMNameSpace="root\sms\site_$SiteName"
    	$QUERY="Select CollectionID from SMS_Collection WHERE Name = '$CollectionName'"
    	Return $(Get-WmiObject -namespace $SCCMNameSpace -computer $SCCMServer -query $QUERY)
    }
    

    Next a function to find all the machines in a collection:

    FUNCTION JBM-SCCM-GetCollectionMembers{
    Param([parameter(Mandatory = $true)]$CollectionID,
    	$SiteName="JBM",
    	$SCCMServer="sccmserver.domain.com")
    	$SCCMNameSpace="root\sms\site_$SiteName"
    	$QUERY="SELECT Name FROM SMS_FullCollectionMembership WHERE CollectionID = '$CollectionID'"
    	Return $(Get-WmiObject -namespace $SCCMNameSpace -computer $SCCMServer -query $QUERY)
    }
    

    Next a function to remove all machines in a collection (the ones that are directly added)

    FUNCTION JBM-SCCM-DeleteAllCollectionMembers{
    Param([parameter(Mandatory = $true)]$CollectionID,
    	$SiteName="JBM",
    	$SCCMServer="sccmserver.domain.com")
    	$SCCMNameSpace="root\sms\site_$SiteName"
        $Collection=[WMI]"\\$($SCCMServer)\$($SCCMNameSpace):SMS_Collection.CollectionID='$CollectionID'"
    	ForEach ($Rule in  $Collection.CollectionRules ){
        $Collection.DeleteMembershipRule($Rule)
        }
    }
    

    Next, a function to add a machine to a Collection:

    FUNCTION JBM-SCCM-AddCollectionMember{
    Param([parameter(Mandatory = $true)]$CollectionID,
    	$SiteName="JBM",
    	$SCCMServer="sccmserver.domain.com",
        [parameter(Mandatory = $true)]$ComputerToAdd)
    	$SCCMNameSpace="root\sms\site_$SiteName"
    
        $computer = gwmi -computer $SCCMServer -namespace $SCCMNameSpace -Query "select * from SMS_R_System where NetBiosName='$ComputerToAdd'"
        $Collection=[WMI]"\\$($SCCMServer)\$($SCCMNameSpace):SMS_Collection.CollectionID='$CollectionID'"
        $ruleClass = [wmiclass]"\\$($SCCMServer)\$($SCCMNameSpace):SMS_CollectionRuleDirect"
        $newRule = $ruleClass.CreateInstance() 
        $newRule.RuleName = $ComputerToAdd 
        $newRule.ResourceClassName = "SMS_R_System" 
        $newRule.ResourceID = $computer.ResourceID 
        $null = $Collection.AddMembershipRule($newRule) 
    }
    

    Finally, a function to copy machines from one collection to another, using the functions above:

    FUNCTION JBM-SCCM-CopyCollectionMembers {
    Param([parameter(Mandatory = $true)]$SourceCollectionName,
        [parameter(Mandatory = $true)]$DestinationCollectionName,
        [parameter(Mandatory = $true)]$NumberToCopy)
    
        $SrcCollID=JBM-SCCM-GetCollection($SourceCollectionName)
        $DstCollID=JBM-SCCM-GetCollection($DestinationCollectionName)
        $SrcCollMembers=JBM-SCCM-GetCollectionMembers($SrcCollID.CollectionID)
        JBM-SCCM-DeleteAllCollectionMembers($DstCollID.CollectionID)
        foreach ($Computer in $($SrcCollMembers | select -first $NumberToCopy)){
        JBM-SCCM-AddCollectionMember -CollectionID $DstCollID.CollectionID -ComputerToAdd $Computer.Name
        }
    
    }
    

    The result – a trickle install.


  • PowerShell script to create a SCCM Package,Program and Queries

    I wanted to automate the process of creating a new package. The following code will look at an XML file for values (same xml file I am using here) and create a package based on the values. A lot of SCCM PowerShell was gathered from here. Here is the example of the source XML file:

    <Install>
    <Params>
    <PkgName Value="Firefox" />
    <PkgManufacturer Value="Mozilla" />
    <PkgVersion Value="12.0" />
    <PkgSourcePath Value="\\domain.local\DFSRoot\Path\Mozilla\Firefox-12.0" />
    <PkgcontainerNodeID Value="17" />
    <CurrentVersionQueryName Value="Software-MozillaFirefox-12.0" />
    <OtherThanQueryName Value="Software-MozillaFirefox-OtherThan-12.0" />
    <QuerycontainerNodeID Value="4" />
    </Params>
    </Install>
    

    And here is the script that creates the package, program, distribution point,and 2 queries (A query to show where version is installed, and a second to show where it is not installed (NOT IN).)

    function JBMMURPHY-SCCM-CreatePackageAndQueries{
    PARAM(
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_})]
        $XMLFilePath)
    [ xml ]$s = Get-Content $XMLFilePath
    
    $NAMESPACE="root\SMS\Site_LAB"
    $PkgName=$s.Install.SCCMParams.PkgName.Value
    $PkgManufacturer=$s.Install.SCCMParams.PkgManufacturer.Value
    $PkgVersion=$s.Install.SCCMParams.PkgVersion.Value
    $PkgSourcePath=$s.Install.SCCMParams.PkgSourcePath.Value
    $PkgcontainerNodeID=$s.Install.SCCMParams.PkgcontainerNodeID.Value
    $OtherThanQueryName=$s.Install.SCCMParams.OtherThanQueryName.Value
    $CurrentVersionQueryName=$s.Install.SCCMParams.CurrentVersionQueryName.Value
    $QuerycontainerNodeID=$s.Install.SCCMParams.QuerycontainerNodeID.Value
    $PkgProgramFlags=135308289
    $PkgCommandLine = "powershell .\SVCInstall.ps1"
    
    ## create package and move into appropriate folder
    $PackageProperties = @{
    Name = $PkgName;
    Manufacturer = $PkgManufacturer;
    Version = $PkgVersion;
    Language = "English (United States)";
    PackageType = 0;
    PkgSourceFlag = 2;
    PkgSourcePath = $PkgSourcePath
    Priority = 2
    }
    $newPackage = Set-WmiInstance -class SMS_Package -arguments $PackageProperties -namespace $NAMESPACE
    $newPackage | Format-List | Out-Null
    write-host "pkgid=$($newPackage.PackageID)"
    
    ## Move Package
    $MovePackage = @{
    InstanceKey = $newPackage.PackageID;
    ObjectType = 2;
    ContainerNodeID = $PkgcontainerNodeID
    }
    Set-WmiInstance -Class SMS_ObjectContainerItem -arguments $MovePackage -namespace $NAMESPACE
    
    ## create new program = Per-system unattended
    
    $NewProgram = @{
    PackageID = $newPackage.PackageID;
    ProgramFlags = $PkgProgramFlags;
    ProgramName = "Per-system unattended";
    CommandLine = $PkgCommandLine
    }
    Set-WmiInstance -class SMS_Program -arguments $NewProgram -namespace $NAMESPACE
    
    ## Add Distribution point to package
    
    $NewDistPointForPackage = @{
    PackageID = $newPackage.PackageID;
    ServerNALPath = '["Display=\\SERVENAME\"]MSWNET:["SMS_SITE=LAB"]\\SERVERNAME\';
    SiteCode = "LAB";
    SiteName = "LAB";
    SourceSite = "LAB";
    ResourceType = "Windows NT Server"
    }
    Set-WmiInstance -class SMS_DistributionPoint -arguments $NewDistPointForPackage -namespace $NAMESPACE
    
    
    ## Creating Queries
    
    ## Current Verson Query
    $CurrentVersionQuery="select SMS_R_System.Name, SMS_R_System.LastLogonUserName, SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName, SMS_G_System_ADD_REMOVE_PROGRAMS.Version from  SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId where SMS_R_System.Client = 1 and SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName like `'$($PkgName)%`' and SMS_G_System_ADD_REMOVE_PROGRAMS.Version = `'$($PkgVersion)`' order by SMS_R_System.Name";
    $CurrentVersionQueryHash = @{
    Name = $CurrentVersionQueryName;
    Expression = $CurrentVersionQuery;
    TargetClassName = "SMS_R_System"
    }
    $newCurrentVersionQuery = Set-WmiInstance -class SMS_Query -arguments $CurrentVersionQueryHash -namespace $NAMESPACE
    $newCurrentVersionQuery  | Format-List | Out-Null
    
    $MoveCurrentVersionQuery=@{
    InstanceKey = $newCurrentVersionQuery.QueryID;
    ObjectType = 7;
    ContainerNodeID = $QuerycontainerNodeID
    }
    Set-WmiInstance -Class SMS_ObjectContainerItem -arguments $MoveCurrentVersionQuery -namespace $NAMESPACE
    
    
    ## Other Than Query
    $OtherThanQuery="select SMS_R_System.Name, SMS_R_System.LastLogonUserName, SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName, SMS_G_System_ADD_REMOVE_PROGRAMS.Version from  SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId where SMS_R_System.Client = 1 and SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName like `'$($PkgName)%`' and SMS_R_System.Name not in (select SMS_R_System.Name from  SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId where SMS_R_System.Client = 1 and SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName like `'$($PkgName)%`' and SMS_G_System_ADD_REMOVE_PROGRAMS.Version = `'$($PkgVersion)`') order by SMS_R_System.Name"
    $OtherThanQueryHash =@{
    Name = $OtherThanQueryName;
    Expression = $OtherThanQuery;
    TargetClassName = "SMS_R_System"
    }
    $newOtherThanQuery = Set-WmiInstance -class SMS_Query -arguments $OtherThanQueryHash -namespace $NAMESPACE
    $newOtherThanQuery  | Format-List | Out-Null
    
    $MoveOtherThanQuery=@{
    InstanceKey = $newOtherThanQuery.QueryID;
    ObjectType = 7;
    ContainerNodeID = $QuerycontainerNodeID
    }
    Set-WmiInstance -Class SMS_ObjectContainerItem -arguments $MoveOtherThanQuery -namespace $NAMESPACE
    
    # To Find Node: gwmi -Namespace $NAMESPACE -query "Select * from SMS_ObjectContainerNode WHERE Name = 'ContainerName'"
    # Example run: JBMURPHY-SCCM-CreatePackage -XMLFilePath \\domain.local\DFSRoot\Path\Install.xml
    #
    }
    

  • PowerShell function to search CRM 2011 for and Entity (via REST/oData)

    I wrote about how to retrieve records from CRM 2011 via oData. I wanted to wrap that up in a function that I can use to do a quick search:

    
    Function JBMURPHY-CRM-SearchEntity{
    PARAM([parameter(Mandatory=$true)][ValidateSet("Contact", "Account","SystemUser")]$EntityType,[parameter(Mandatory=$true)]$SearchString,[parameter(Mandatory=$true)]$SearchField,$FieldsToReturn)
    $assembly = [Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")
    $baseurl="http://crmserver.company.com/Organization/xrmservices/2011/OrganizationData.svc"
    $urlparams="/$($EntityType)Set?`$filter=substringof('$($SearchString)',$($SearchField))"
    $url=$baseurl+$urlparams
    $Count=0
    while ($url){
        $webclient = new-object System.Net.WebClient
        $webclient.UseDefaultCredentials = $true
        $webclient.Headers.Add("Accept", "application/json")
        $webclient.Headers.Add("Content-Type", "application/json; charset=utf-8");
        $dataString=$webclient.DownloadString($url)
        $json=new-object System.Web.Script.Serialization.JavaScriptSerializer
        $data=$json.DeserializeObject($dataString)
        foreach ($result in $data.d.results){
                $Count=$Count+1
                write-host -NoNewline "$Count. "
                foreach ($field in $FieldsToReturn){
                write-host -NoNewline "$field : "
                Write-Host -NoNewline $result."$field"
                Write-Host -NoNewline ", "
                #write-host "$($result.FullName) , $($result.EMailAddress1)"
                }
                Write-Host
        }
        if ($data.d.__next){
            $url=$data.d.__next.ToString()
        }
        else {
            $url=$null
        }
    }
    }
    
    

    And to use this function:

    JBMURPHY-CRM-SearchEntity -EntityType Account -SearchField Name -SearchString “Sard” -FieldsToReturn Name,AccountId


  • Using PowerShell to query CRM 2011 SOAP endpoint – Answer!

    Big thanks to @JLattimer. He helped me figure our the error in my SOAP envelope.

    In my last post, I was trying to retrieve FullName from Crm 2011’s web services via PowerShell. Below is the code to do that.

    $xml = "<?xml version='1.0' encoding='utf-8'?>"
    $xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"
    $xml += "<soap:Body>"
    $xml += "<Retrieve xmlns='http://schemas.microsoft.com/xrm/2011/Contracts/Services'>"
    $xml += "<entityName>contact</entityName>"
    $xml += "<id>12345678-1234-1234-1234-123456789012</id>"
    $xml += "<columnSet xmlns:q1='http://schemas.microsoft.com/xrm/2011/Contracts' xsi:type='q1:ColumnSet'>"
    $xml += "<q1:Columns xmlns:c='http://schemas.microsoft.com/2003/10/Serialization/Arrays'>"
    $xml += "<c:string>fullname</c:string>"
    $xml += "<c:string>telephone1</c:string>"
    $xml += "</q1:Columns>"
    $xml += "</columnSet>"
    $xml += "</Retrieve></soap:Body></soap:Envelope>"
    
    $url="http://crmserver.company.com/Organization/XRMServices/2011/Organization.svc/web"
    
    $http_request = New-Object -ComObject Msxml2.XMLHTTP
    $http_request.Open('POST', $url, $false)
    $http_request.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Retrieve");
    $http_request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    $http_request.setRequestHeader("Content-Length", $xml.length);
    $http_request.send($xml);
    $http_request.responseText
    [ xml ]$results=$http_request.responseXML.xml
    $ns = New-Object Xml.XmlNamespaceManager $results.NameTable
    $ns.AddNamespace( "b", "http://schemas.microsoft.com/xrm/2011/Contracts" )
    $ns.AddNamespace( "c", "http://schemas.datacontract.org/2004/07/System.Collections.Generic" )
    $FullName=$results.selectSingleNode("//b:KeyValuePairOfstringanyType[c:key='fullname']/c:value/text()",$ns).Value
    $Telephone=$results.selectSingleNode("//b:KeyValuePairOfstringanyType[c:key='telephone1']/c:value/text()",$ns).Value
    

  • Using PowerShell to query CRM 2011 SOAP endpoint – help!

    I don’t know what I am doing wrong. All I am trying to do is to query  CRM 2011 via PowerShell and SOAP. The following PowerShell script should return the Contact’s full name, but I get nothing. Any ideas??

    Here is the PowerShell:

    $xml = "<?xml version='1.0' encoding='utf-8'?>"
    $xml += "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"
    $xml += "<soap:Body>"
    $xml += "<Retrieve xmlns='http://schemas.microsoft.com/xrm/2011/Contracts/Services'>"
    $xml += "<entityName>contact</entityName>"
    $xml += "<id>12345678-1234-1234-1234-123456789012</id>"
    $xml += "<columnSet xmlns:q1='http://schemas.microsoft.com/xrm/2011/Contracts' xsi:type='q1:ColumnSet'>"
    $xml += "<q1:Attributes>"
    $xml += "<q1:Attribute>fullname</q1:Attribute>"
    $xml += "<q1:Attribute>telephone1</q1:Attribute>"
    $xml += "</q1:Attributes>"
    $xml += "</columnSet>"
    $xml += "</Retrieve></soap:Body></soap:Envelope>"
    
    $url="http://crmserver.company.com/Organization/XRMServices/2011/Organization.svc/web"
    $http_request = New-Object -ComObject Msxml2.XMLHTTP
    $http_request.Open('POST', $url, $false)
    $http_request.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Retrieve");
    $http_request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    $http_request.setRequestHeader("Content-Length", $xml.length);
    $http_request.send($xml);
    $http_request.responseText
    

    And here is the response:

    $http_request.responseText
    
    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body><RetrieveResponse xmlns="http://schemas.microsoft.com/xrm/2011/Contracts/Services">
    <RetrieveResult xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
    <a:KeyValuePairOfstringanyType>
    <b:key>accountid</b:key>
    <b:value i:type="c:guid" xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/">12345678-1234-1234-1234-123456789012</b:value>
    </a:KeyValuePairOfstringanyType>
    </a:Attributes>
    <a:EntityState i:nil="true"/><a:FormattedValues xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic"/>
    <a:Id>12345678-1234-1234-1234-123456789012</a:Id><a:LogicalName>account</a:LogicalName>
    <a:RelatedEntities xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic"/>
    </RetrieveResult>
    </RetrieveResponse>
    

    I just don’t know what I am doing wrong. Why won’t the code return “fullname”, it is in the columnSet.

    Update-2012-05-08:I have posted my question here, no answer yet

    Update2-2012-05-05: I have posted and received and answer here on stack overflow. Big thanks to @JLattimer


  • How to get the count of items returned in jQuery Autocomplete

    In this post I used jQuery to autocomplete an input box with results from CRM 2011. I wanted to give the number of results returned for the particular autocomplete query. I chose to use the autocomplete open: parameter to get the number of results returned, and I put that result in a div with an ID of “Count”

     

    open: function(event,ui){
    	    var len = $(this).autocomplete("widget").find( "li" ).length;
    	    var resultText='RESULTS';
    	    if (len==1) resultText='RESULT'
    	    $('#Count').text('FOUND '+len+' '+resultText);
    	  	},
    close: function(event,ui){
    	    $('#Count').text('ENTERPRISE SEARCH');
      	}
    
    

  • PowerShell script to copy a SharePoint Document Library to a Local Path

    I wanted to have a PowerShell script that downloaded A Document Library’s contents to a local folder. This is what I put together.

    Function JBM-SharePoint-CopyDocumentLibraryLocal {
    PARAM([Parameter(Mandatory=$true)]$SiteURL,
        [Parameter(Mandatory=$true)]$DocumentLibrary,
        [Parameter(Mandatory=$true)]$Destination)
    $spWeb = Get-SPWeb -Identity http://$SiteURL
    $list = $spWeb.Lists[$DocumentLibrary]
    foreach ($listItem in $list.Items)
    {
        $DestinationPath = $listItem.Url.replace("$DocumentLibrary","$Destination").Replace("/","\")
        write-host "Downloading $($listItem.Name) -> $DestinationPath"
    
        if (!(Test-Path -path $(Split-Path $DestinationPath -parent)))
        {
            write-host "Createing $(Split-Path $DestinationPath -parent)"
            $dest = New-Item $(Split-Path $DestinationPath -parent) -type directory
        }
        $binary=$spWeb.GetFile($listItem.Url).OpenBinary()
        $stream = New-Object System.IO.FileStream($DestinationPath), Create
        $writer = New-Object System.IO.BinaryWriter($stream)
        $writer.write($binary)
        $writer.Close()
    }
    $spWeb.Dispose()
    

  • Update DynDNS from the command line

    My router is not updating DynDNS correctly, so I wanted to use a simple cron job to do the same. Here is a simple BASH/Shell script to update DynDNS:

    IP=$(curl -s icanhazip.com)
    curl -v -k -u username:password “https://members.dyndns.org/nic/update?hostname=DNSHOSTNAME.dyndns.org&myip=$IP”