Wednesday 30 October 2013

Delete documents from SharePoint document library using PowerShell

So today we had to delete a bunch of documents from SharePoint, so I wrote this script to accomplish this task.

A couple of notes about the script:

  1. By default it will not delete anything, just list the urls that contain the $document argument.
  2. The $document argument is matched with like as it's matching urls, so be careful that it doesn't delete more documents than you want.


param
(
[string]$url = "https://sp.dev.local",
[string]$document = "Welcome"
[bool]$delete = false
)

$site = Get-SPSite $url

foreach($web in $site.AllWebs) {
    foreach($list in $web.Lists) {
              if($list.BaseType -eq "DocumentLibrary") {
                     foreach($item in $list.Items){
                           if($item.url -like "*$document*")
                           {
                                  if($delete)
                                  {
                                   Write-Host "Deleting $item.url"
                                   $item.File.Delete()
                                  }
                                  else
                                  {
                                   Write-Host "$item.url"
                                  }
                         }               
                      }
              }
       }
}

 

Friday 25 October 2013

Issues with TurnOffFetchThrottling in MS Dynamics CRM 2011 - FetchXml Record Limit

I think i found a bug in MS Dynamics CRM 2011 today. We have a few UR 12 installed on our servers and for various reasons too long too explain, we had Turned off Fetch throttling.

This is done by adding a registry key to HKEY_LOCAL_MACHINE\Software\Microsoft\MSCRM called TurnOffFetchThrottling and setting the value to 1. This needs to be a DWORD type.

If you do this, then this FetchXml query will not work:
<fetch mapping="logical" count="2147483647" version="1.0"> 
 <entity name="account">
  <attribute name="name" />
 </entity>
</fetch>
It seems that by default MS Dynamics CRM 2011 will add one to the number of results that one is trying to get. So with Fetch Throttling on, i.e. the default, the above query will be parsed to the database like this:
select top 5001 "account0".Name as "name" , "account0".AccountId as "accountid" from AccountBase as "account0" order by "account0".AccountId asc
But if, FetchThrottling is off, when we pass Int.Max32 as the number of results required, like in the fetchxml query above, then when Dynamics CRM tries to add one to this value and parse it, it overflows and the query isn't run.

Admittedly it is extremely unlikely that this will be a problem, if you have 2147483647 instances of an entity in your database, I rather suspect that you need to be doing some archiving, but still it looks like a genuine issue.

It's interesting that the count value uses a signed integer rather than an unsigned one, which would give us twice as many records, i.e. 4294967295, or one record per every two human beings on Earth.

Sunday 20 October 2013

Developers Vs Developers

I have been meaning to write this post for ages as this is something that I have encountered time and time again during my career.

We have an integration layer on our application with our telephony system. A third party wrote this integration layer, in essence they have a web services that we communicate to and we expose a web service so that they can communicate with us, so far so simple, this is a staple of enterprise development and as I said I have had to deal with situations like this many times, both inside and outside the company, and if you've not done it inside your company and think that none of the problems that you are having would occur if you didn't have to deal with the idiots at Third Part Ltd., let me tell you that you will just have to deal with the idiots at the Other Department.

At any rate, integration was working fine when somebody realized that the spec called for SSL/TLS usage rather than using clear text. In theory this requirement made sense when the various sides of the integration equation where hosted by two different companies, but a change of architecture had been made, which meant that they weren't any longer, so using a secure website for an internal web interface that contained little more than phone numbers and UUIDs seemed like overkill, but the spec is the spec, agile be dammed.

So both Web Services and client apps on either side of the integration equation where reconfigured to use SSL/TLS and this is were the problems started and as it's normally the case in these situations, we started blaming each other. 

Furthermore, a supplier customer relationship had been allowed to develop. This is a relationship in which the supplier has the technical know how, or believes he does, and the customer doesn't, or is at least believed not to have it by the supplier. Needless to say that this wasn't the case as we shall see, but for various personnel reasons, i.e. developers on our company leaving, this relationship had taken hold, which meant that our evidence carried less weight as it were, because they were already predisposed to assuming that they were talking to yet another developer who didn't have a clue about how the integration was supposed to be working, which wasn't true but it was true that they knew their system and the history, and while I knew ours but could not comment on historical decisions as this had landed on my lap without handover from somebody that left the company suddenly, not so suddenly but I digress.

We both went back to basis to prove our respective thesis, i.e. it was the other party's fault, because history is written by the victors, you know how this will turn out, but I will continue anyway. The first thing that we discovered was another disagreement in the spec, regarding authentication, which I remedied after a lot of hair pulling.

After that, I found that our side was working fine, furthermore, I used Wireshark to prove that nothing was coming through over the wire on port 443, while stuff was going through on port 80 from the client PC that hosted the client app, which meant that failures on their side were not due to our Web Service, this despite the fact that the app was throwing a 404 error and pointed me to this link.

I mentioned the supplier customer relationship above, because it helps to explain why this evidence, our Wireshark evidence was ignored, they knew what they were doing we didn't so anything coming from our side was tainted.

To further compound the confusion, the client app would sometimes work when tested by them, which made us extremely suspicious and not work for us, which surely made them more suspicious of our general competence. It was working for them and they were dealing with a bunch of idiots so they probably were doing something stupid.

At this point they were willing to discuss the code that they were using for the first time and we realized that the source of the issue was their code, to be fair it was the usage of our proxy server, when it should not have been used, there were other issues but they are not important.

So I asked them to add this to the system.net element of the client's app.config:

<defaultProxy  enabled="false"/>

Lo and behold, everything started to work fine.

Not sure what the lesson is here, I guess if there is one, it's that appearances do matter even in a supposedly analytical field like this one.

This link seems tangentially related.

Tuesday 15 October 2013

Start Windows Services from PowerShell according to StartMode.

In a previous post, I described how to stop and start MS Dynamics CRM services, this post is just a different way of doing it. The reason for looking for a different way is that the Get-Services cmdlet ignores the StartMode, i.e. if the service is disabled Start-Service will try to start it and fail so the solution is using WMI objects:
Get-WmiObject -Class "win32_service" | ?{$_.Name -like "mscrm*" -and $_.StartMode -eq "Auto"} | % {Restart-Service -Name $_.Name}‏
You might be wondering why this is needed, surely you should just uninstall the disabled service(s) and I would tend to agree, but sometimes it might be necessary to temporarily disable a service for testing purposes for instance, and by checking for the startmode you will only attempt to start services that can actually be restarted. 

Thursday 10 October 2013

Using Host headers for SSL/TLS in SSRS 2008 R2

In a project I 'm working on at the moment, we are using SSRS over SSL and this had been working fine but we were using self signed certificates, so when we changed the certificates we started getting some issues, and by same issues, i mean the reports not working properly, as there was a mismatch between the cert name and the server name. We could not navigate to the Report Server or Report Manager URL, did not note down the exact error message, but it was related to a cert name mismatch causing the connection to be closed.

The logs would should this error:
appdomainmanager!ReportManager_0-3!1a88!10/04/2013-16:22:14:: e ERROR: Remote certificate error RemoteCertificateNameMismatch encountered for url https://ssrsserver.domain.co.uk:501/ReportServer/ReportService2010.asmx.
ui!ReportManager_0-3!1a88!10/04/2013-16:22:14:: e ERROR: System.Threading.ThreadAbortException: Thread was being aborted.
   at System.Threading.Thread.AbortInternal()
   at System.Threading.Thread.Abort(Object stateInfo)
   at System.Web.HttpResponse.End()
   at Microsoft.ReportingServices.UI.ReportingPage.ShowErrorPage(String errMsg)

In order to sort this problem I first configured a host header to match the certificate name and then modified the reportserver configuration file.

1. Run the Reporting Services Configuration Manager.


 2. Select Web Service Url.



 3. Click on Advanced to configure the Web Service Url.


 4. Select http entity and click edit to add the host header.


 5. Repeat steps 3 & 4 for Report Manager.



6. Edit C:\Program Files\Microsoft SQL Server\MSRS10.MSSQLSERVER\Report Services\ReportServer\rsreportserver.config:
Change the urlstring to reflect the new host header e.g.
   <UrlString>http://mydomain.co.uk:80</UrlString>
Make sure that you only change the entries that actually have a domain name and are not just showing a url registration. In other words, leave these alone:
<UrlString>https://+:443</UrlString>
Having said, changing these might allow to use different host headers in plaintext and secure websites, but I've not tried it.

Saturday 5 October 2013

Deploy SharePoint Event Receivers from PowerShell

I thought I would share a script that we use for deploying Event Receivers to SharePoint.

param ([string] $url, [string] $featureName,[string] $solution, [bool] $install, [bool] $uninstall)

if ( -not ($url))
{
 Write-Host "Please enter the Site Url"
 exit
}

function SelectOperation
{
 $message = "Select Operation?";
 $InstallMe = new-Object System.Management.Automation.Host.ChoiceDescription "&Install","Install";
 $UninstallMe = new-Object System.Management.Automation.Host.ChoiceDescription "&Uninstall","Uninstall";
 $choices = [System.Management.Automation.Host.ChoiceDescription[]]($InstallMe,$UninstallMe);
 $answer = $host.ui.PromptForChoice($caption,$message,$choices,0)
 return $answer
}

if (-not($install) -and -not($uninstall))
{
 $answer = SelectOperation

 switch ([int]$answer)
 {
   0 {$install=$true}
   1 {$uninstall=$true}
 }

}

if ($install)
{
 Add-SPSolution -LiteralPath $(join-path $(pwd).Path $solution)
 $solutionId = (Get-SPSolution | ? {$_.name -eq  $solution}).Id
 Install-SPSolution -Identity $solutionId -GACDeployment
 Write-Host "Waiting for the SharePoint to finish..."
 Sleep(120)
 $featureNameId = Get-SPfeatureName | where {$_.displayname -eq  $featureName} 
 Enable-SPfeatureName -Identity $featureNameId -Url $url
}

if ($uninstall)
{
 $featureNameId = (Get-SPfeatureName | where {$_.displayname -eq  $featureName}).Id
 Disable-SPfeatureName $featureNameId -Url $url
 $solutionId = (Get-SPSolution | ? {$_.name -eq  $solution}).Id
 Uninstall-SPSolution -Identity $solutionId
 Write-Host "Waiting for SharePoint to finish..."
 Sleep(120)
 Remove-SPSolution -Identity $solutionId
}
Write-host "All Done."

An example of usage:
.\DeployEV.ps1 -featureName "receiver1" -solution "receiver1.wsp"  -install $true
 Note that the script assumes that the wsp file will be in the same location as the script.