søndag den 23. oktober 2011

Deploy CRM List Component though PowerShell

as always I did a few searches on Google. Most hits just explained how to upload the WSP file and then call the AllowHtcExtn.ps1 script.

On blog did explain you could use Add-SPSolution and Install-SPSolution but that didn’t work for me.

$solution = Get-SPSolution crmlistcomponent.wsp
if(!$solution){ $solution = Add-SPSolution X:\SPS_2010_prov\listekomponent\en\crmlistcomponent.wsp }
Install
-SPSolution -WebApplication "https://$webappurl" $solution -GACDeployment -Force

The solution didn’t show up in Solution Gallery inside SharePoint. And when I tested from CRM 2011 it kept complaining the List Component isn't installed.


I have just been to Microsoft Campus Days in Copenhagen ( kind of a “mini TechEd” ) and one of the session I was attending was about SharePoint Online and how Microsoft only allow Sandbox solutions, and then it hit me. Maybe the CRM list Component is a sandbox solution and needs to be deployed that way. We have a winner!


So we end up with


Write-Host "Starting Sharepoint User Code Service"
stsadm
-o provisionservice -action start -servicetype Microsoft.SharePoint.Administration.SPUserCodeService -servicename SPUserCodeV4

Write
-Host "Install crmlistcomponent if needed"
$solution = Get-SPUserSolution crmlistcomponent.wsp -Site "https://$webappurl" -ea 0
if(!$solution){
$temp = Add-SPUserSolution X:\SPS_2010_prov\listekomponent\en\crmlistcomponent.wsp
$solution = Get-SPUserSolution crmlistcomponent.wsp -Site "https://$webappurl"
}

Write
-Host "Active crmlistcomponent if needed"
if($solution.Status -ne 'Activated'){
$solution = Install-SPUserSolution $solution -Site "https://$webappurl"
}

Write
-Host "Make sure .htc file name extension is an allowed file type"
$app = Get-SPWebApplication -Identity "https://$webappurl"
$app.AllowedInlineDownloadedMimeTypes.Add("text/x-component")
if(!$app.WebFileExtensions.Contains("htc")){
Write
-Host "Adding .htc file name extension to the list of allowed file types."
$app.WebFileExtensions.Add("htc")
$app.Update()
}

lørdag den 22. oktober 2011

Find owner of Fulltext catalog

Once in a while I would get this error when dropping SQL logins

The database principal owns a fulltext catalog in the database, and cannot be dropped.

Changing the owner is easy enough, but I’m doing this by scripts and needed a way to find the owner of the fulltext catalog. Took a while but finally managed to get the result with

select cat.fulltext_catalog_id, cat.name 
from sys.fulltext_catalogs as cat
inner join sys.database_principals as dp2
on cat.principal_id = dp2.principal_id


Perfect, so lets script that, and we get something like


$SQLLogin = 'soadmin_SOTEST2'
$sqlserver = 'sql01.int.wingu.dk'
$database = 'SO7_SO7SOTEST2'

$sql = "select cat.fulltext_catalog_id, cat.name from sys.fulltext_catalogs as cat "
$sql = ($sql + " inner join sys.database_principals as dp2 on cat.principal_id = dp2.principal_id ")
$sql = ($sql + "where dp2.name = '$SQLLogin'")

$rs = Invoke-Sqlcmd -ServerInstance $sqlserver -Database $database -Query $sql
foreach($row in $rs){
$sql = ("ALTER AUTHORIZATION ON Fulltext Catalog::[" + $row.Name + "] TO dbo")
Invoke
-Sqlcmd -ServerInstance $sqlserver -Database $database -Query $sql
}

mandag den 10. oktober 2011

Working with SuperOffice though PowerShell

This powershell snap in is not meant to be a complete SuperOffice client, but for most basic stuff you can use this. Like say, you want to run a batch job everyday to update/sync information's from anther system and SuperOffice

working with powershell makes a lot of things a lot easier. Lets have a look at a few examples

First lets connect to SuperOffice. If you have the superoffice.config from your client laying around somewhere you can easily connect using that.

$module = Get-Module | where {$_.name -eq 'wingu.SuperOffice.SnapIn'}
if($module -eq $null){
$module = Get-Module -ListAvailable | where {$_.name -eq 'wingu.SuperOffice.SnapIn'}
if($module){ import-module wingu.SuperOffice.SnapIn }
}

$config = Get-SO7config c:\superoffice.config | Set-SO7config
$config.Username = $config.Explicit.DBUser
$config.Password = $config.Explicit.DBPassword

So, just to validate everything is working, lets get the license information
$LicenseInfo = Get-SO7LicenseInfo
# To replace running license use
#
Set-SO7LicenseInfo -CompanyName 'Happy Ducks Holding ApS' -SerialNumber '1234'
#
To Update current license. ( like if you buy more user licenses )
#
Set-SO7LicenseInfo $LicenseInfo

To see what credentials is associated with alz@sotest1.local and if he is logged on right now, you could so something like this


Get-SO7Person -email alz@sotest1.local | Get-SO7Credentials

Or to have a loot at who is using SuperOffice right now
Get-SO7Credentials | where {$_.CredentialType -eq 'Ticket' }

( this will not be accurate, since cleanup of ticket’s is only done when a user logs in. but still … )

Do you want a list of Persons asscociated with Contact ‘hestehøj a/s’ ?


Get-SO7Contact 'Hestejøj AS' | Get-SO7Person

Want to create a new Associate, with AD login ?


$logonName = 'alz'
$upn = 'alz@sotest1.local'
$FirstName = 'Allan'
$Lastname = 'Zimmermann'

# Get Associate if exists
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
# Get Person with email corrosonding to upn, if exists
$CRM7Person = Get-SO7Person -email $upn -ea 0
if((!$CRM7User) -and (!$CRM7Person)){
# could not find a person nor Associate so create both
$CRM7User = New-SO7Associate -FirstName $FirstName -Lastname $Lastname -logonName $logonName -Email $upn -AdUser
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
$CRM7Person = Get-SO7Person -email $upn -ea 0
}
if((!$CRM7User) -and ($CRM7Person)){
# Person exists, but have no Associate so create it and link to person
$CRM7User = New-SO7Associate -person $CRM7Person -logonName $logonName -Email $upn -AdUser
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
}
# Assign a UserLicense, allow login, and enable Windows/web
$CRM7User | Enable-SO7Associate

Or, create with a CRM5 password (not ad login) ?
$logonName = 'alz'
$upn = 'alz@sotest1.local'
$FirstName = 'Allan'
$Lastname = 'Zimmermann'
$Password = 'Passw0rd'

# Get Associate if exists
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
# Get Person with email corrosonding to upn, if exists
$CRM7Person = Get-SO7Person -email $upn -ea 0
if((!$CRM7User) -and (!$CRM7Person)){
# could not find a person nor Associate so create both
$CRM7User = New-SO7Associate -FirstName $FirstName -Lastname $Lastname -logonName $logonName -Email $upn -Password $Password
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
$CRM7Person = Get-SO7Person -email $upn -ea 0
}
if((!$CRM7User) -and ($CRM7Person)){
# Person exists, but have no Associate so create it and link to person
$CRM7User = New-SO7Associate -person $CRM7Person -logonName $logonName -Email $upn -Password $Password
$CRM7User = Get-SO7Associate -logonName $logonName -ea 0
}
# Assign a UserLicense, allow login, and enable Windows/web
$CRM7User | Enable-SO7Associate

anyway, here is a complete list of commands.
Get-Command -Module wingu.SuperOffice.SnapIn


Close-SO7Session
Decrypt-SO7H
Disable-SO7Associate
Enable-SO7Associate
Encrypt-SO7H
Get-MySO7Identity
Get-SO7Associate
Get-SO7Business
Get-SO7config
Get-SO7Contact
Get-SO7Country
Get-SO7Credentials
Get-SO7Email
Get-SO7LicenseInfo
Get-SO7Person
Get-SO7Secret
Get-SO7SystemInfo
New-SmartProcess
New-SO7Associate
New-SO7Business
New-SO7Contact
New-SO7Credentials
New-SO7Person
Remove-SO7Associate
Remove-SO7Business
Remove-SO7config
Remove-SO7Contact
Remove-SO7Credentials
Remove-SO7Email
Remove-SO7Person
Save-SO7config
Set-SO7config
Set-SO7LicenseInfo
Set-SO7Person


lørdag den 8. oktober 2011

SharePoint and WebDAV–part 2

So I was troubleshooting mapping SharePoint though WebDAV and found I had to open the SharePoint site though office in one way or the other, in order to successfully map it. That was true, but for one very specific reason.

Talking about cookies, you need to know there is 2 kinds of cookies. Session Cookies and Persistent cookies. Session cookies only “lives” as long as your browser is open. As soon as you close *ALL* your browser windows they cookie will expire. Persistent cookies will stay on your machine until the expire date on the cookie is hit.

When you setup SharePoint to use Claimsbased authentication SharePoint save a cookie on your machine called the “FedAuth” cookie. This is your what authenticate you every time you hit SharePoint. It gets set when the Identity Provider (STS/ADFS) makes your browser “post” your claims to https://fqdn/_trust/ .It gets removed when you click “sign out” or “Sign in as Different User”.

The WebDAV client, and various Office applications does support working with websites that facilitate Claimsbased authentication, but they cannot authenticate you. It assumes you have a FedAuth cookie on your machine. Some times you will get a browser popup, but there are places this wont happen (like with WebDAV). I had told SharePoint to use Session Cookies, since that would make more sense when working with SharePoint. You login you do your stuff, you might sign out, or you might close your browser. Next time you hit it, it asks you to login. I was happy, everyone else was happy. That is, until someone needed to work with SharePoint from within Outlook and though WebDAV.

SharePoint will standardly run with Persistent Cookies, but you can change this though PowerShell. Set UseSessionCookies to $true or $false. If you need to work with SharePoint though Office or WebDAV you need to set this to $false (default)

$snapin = Get-PSSnapin | where {$_.name -eq 'Microsoft.SharePoint.PowerShell'}
if($snapin -eq $null){ Add-PSSnapin Microsoft.SharePoint.PowerShell }

$sts = Get-SPSecurityTokenServiceConfig
$sts.UseSessionCookies = $false
$sts.Update()
iisreset

As far as I know, you cannot set this per site.


Next you want to make sure Office/Web Client can read the cookie. Referee to “Persistent cookies are not shared between Internet Explorer and Office applications

onsdag den 5. oktober 2011

First encounter with SuperOffice 7 PocketCRM

Apparently some people inside the company I work for, have told some one else that I have had a bit of experience with SuperOffice. One of our SuperOffice consultants was troubleshooting a failed SuperOffice 7 PocketCRM installation, and needed some help getting it up and running. I have never seen that product but I never back down from a good challenge.

The first thing I noticed when logging on to the server, was that SuperOffice web was installed, and didn’t work. If you browsed the local website, no matter what you wrote you would just get an HTTP 404 file not found. The weird thing was the error came from a Tomcat server and not IIS. I did a net stat –ano to see if Tomcat had “stolen” port 80, but that was not the case. After looking around a bit inside IIS, I noticed there was added an ISAPI filter pointing to a DLL inside the tomcat directory. After removing that entry, IIS worked again, and we could login to SuperOffice web.

But now Pocket CRM wasn’t working. Knowing that the ISAPI DLL was “eating” all web request, I  decided to created a new website and added the ISAPI filter to that website, and I also added a virtual directory pointing to Jakarta as it had been doing on the original website. browsing to the new website’s /dl I would now be presented with the option to download the pocket CRM client. We tested if we could login from a client, but had no luck. After several different test’s we managed to get an error from the client saying it couldn’t connect to http://fqdn/NetServer/soPrincipal . I ran the installer for SoWeb and created a new NetServer installation on the main website (the one with SoWeb) with a virtual directory called /NetServer. Ran config and we tested the CRM Pocket client again.

Now we would just get login failed, no matter what. I enabled logging on the NetServer installation, but it wouldn’t generate any log.

<SuperOffice>
<Diagnostics>
<add key="LogError" value="True" />
<add key="LogWarning" value="True" />
<add key="LogFailureAudit" value="True" />
<add key="LogSuccessAudit" value="True" />
<add key="LogInformation" value="False" />
<add key="LogToFile" value="True" />
<add key="LogFolder" value="c:\temp" />
</Diagnostics>
</SuperOffice>

Weird. I opened visual studio on my own machine, added a reference to the above server and created a small test application
    Sub Main()
Dim WS As New ServiceReference1.SoPrincipalClient
Dim Password As String = "secret", UserName As String = "user"
Dim Succeeded As Boolean, TimeZone As ServiceReference1.SoTimeZone, Response As ServiceReference1.SoPrincipalCarrier
Dim AuthenticationSucceeded
Dim Credentials As ServiceReference1.SoCredentials
Dim WsClient As test.ServiceReference1.SoExceptionInfo = WS.AuthenticateUsernamePassword(Password, UserName, _
Succeeded, TimeZone, Response, AuthenticationSucceeded, Credentials)
Console.WriteLine(
"Succeeded: " & Succeeded)
Console.WriteLine(
"AuthenticationSucceeded: " & AuthenticationSucceeded)
If Credentials Is Nothing Then
Console.Write(
"Credentials is nothing")
Else
Console.Write(
"Credentials Ticket: " & Credentials.Ticket)
End If
Dim s As String = ""
Console.WriteLine(
"done.")
Console.ReadLine()
End Sub


I cant re-generate the error message, to show here on the blog, but it said something about about not being able to bind the web service to port “http”. I have seen that error before so I opened web.config and added


<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

After that I could now successfully authenticate to the NetServer, so we went back to the SuperOffice client. Same stuff, Client would say “authentication failed” and NetServer was net generating any log.

I went back to my application and wrote a wrong username and password. a log file was created. I delete the logs, chanced username/password to some valid credentials and ran the application again. no log file got generated … So seems SuperOffice has a bug and isn't logging successful logins when told too. We did the same test from the PocketCRM client, and saw the same thing happen. It would log when typing wrong username and password, and no log when we typed valid credentials. But the client kept saying “authentication failed”


After enabling debug logging inside tomcat I noticed PocketCRM wasn’t talking with NetServer though Tomcat, so I had the weird idea that maybe the PocketCRM application inside Tomcat didn’t talk with the correct NetServer


After looking though different files, I found \SuperOffice\SuperOffice 7 Server\Modules\PocketCRM\Tomcat 6.0\webapps\#pocketcrm\WEB-INF\classes and saw the NetServer URL was wrong. I changed it, and restarted PocketCRM, and voila. Everything worked.

søndag den 2. oktober 2011

Managing CRM 2011 with powershell

New updated version to better handle kerberos authentication, support for CRM online, and CRM 2011 and 2013. read more here
I see a lot of hits from search engines where people are searching for stuff related to Microsoft CRM 2011 and powershell. So I decided to upload the snap in a wrote about, a few months ago.

The problem with CRM 2011 is that the powershell commands that comes with CRM 2011 only works on the local machine. but even worse, you can do anything from the commands except doing basic configuration of the installation it self. obvious stuff like adding users and changing roles isn't supported.

So here is a PowerShell Snap in (works as module too) that does exactly all that. It works on any machine that have .NET 4.0 and powershell 2.0 installed. Just copy the \debug folder to C:\Windows\System32\WindowsPowerShell\v1.0\Modules\CRM2011.Module open powershell and type “import-Module CRM2011.Module” and your ready to rock.

  • Add-CRMUserRole
  • Disable-CRMSystemUser
  • Enable-CRMSystemUser
  • Get-CrmLanguage
  • Get-CrmOrganization
  • Get-CrmOrganizationLanguage
  • Get-CrmRole
  • Get-CRMSystemUser
  • Get-CRMSystemUserSettings
  • Get-CRMUserRole
  • New-CRMCustomerAdminRole
  • New-CrmOrganization
  • New-CrmOrganizationLanguage
  • New-CRMSystemUser
  • Remove-CrmOrganization
  • Remove-CrmOrganizationLanguage
  • Set-CrmOrganization
  • Set-CRMSystemUser
  • Set-CRMSystemUserSettings
  • Update-CRMSystemUser

lørdag den 1. oktober 2011

Getting output from process spawned from powershell

Updated 02-10-2011: Fixed a small bug that could make SmartProcess consume 100%
There is several ways you can spawn a new process from within PowerShell. Start-Process for instance. If you create a new object of type System.Diagnostics.Process or call Start-Process and get such an object back, you can now get access to the Standard Input/Standard Output … there is a “catch” thou. These only get updated every time the process sends a complete line including a newline.

So lets say you spawn nslookup and want to catch every time i t expects input, it wont work.

function StartProcess([String]$FileName){
$process = New-Object "System.Diagnostics.Process"
$startinfo = New-Object "System.Diagnostics.ProcessStartInfo"
$startinfo.FileName = $FileName
#$startinfo.Arguments = $arguments
#$startinfo.WorkingDirectory = $pwd.Path
$startinfo.UseShellExecute = $false
$startinfo.RedirectStandardInput = $true
$startinfo.RedirectStandardOutput = $true
$startinfo.RedirectStandardError = $false
#$startinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$startinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal

$process.StartInfo = $startinfo
$temp = $process.start()
return $process
}

function GetPrompt([System.Diagnostics.Process]$process, [Int]$waitSec){
$str = ''
$i = 1
while( (!([String]$str).contains("> ")) -and (!$process.HasExited) -and ($i -le $waitSec) -and ($process.StandardOutput.Peek() -eq -1) ){
# Write-Host ("Peek: " + $process.StandardOutput.Peek())
while($process.StandardOutput.Peek() -ge 0){
$iChar = $process.StandardOutput.Read()
$str = ($str + [Convert]::ToChar($iChar))
}
if(!([String]$str).contains("> ")){ Write-Host "."; Start-Sleep -s 1; }
$i = $i + 1
}
return $str
}

if($true){
#[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
#[System.Windows.Forms.SendKeys]::SendWait("skadefro.dk")

if(!$process.HasExited){ Write-Host "Killing process"; $process.kill(); $process.StandardOutput.ReadToEnd() }
$process = StartProcess 'nslookup'
GetPrompt
$process 2
$process.StandardInput.WriteLine('set q=MX')
GetPrompt
$process 2
$process.StandardInput.WriteLine('skadefro.dk')
$process.StandardInput.Flush()
#$process.StandardOutput.ReadToEnd()
GetPrompt $process 2

}
if(!$process.HasExited){ $process.kill() }


So you could write some code and hook up to the ErrorDataReceived and OutputDataReceived events. but those have the same problem, so you need to dig a bit deeper. You need to take each stream object and hook up to BaseStream.BeginRead


So that is what I have done. Inside my SuperOffice Add in I have added a new command called New-SmartProcess


And now the above code will look cleaner and actually work


function WaitForPrompt([wingu.SmartProcess]$process, [Int]$waitSec){
$str = ''
$i = 1
while( (!($process.StandardOutput.contains("> ")) -and (!$process.HasExited) -and ($i -le $waitSec))){
if(!([String]$str).contains("> ")){ Write-Host "."; Start-Sleep -s 1; }
$i = $i + 1
}
return $str
}

if($process){ if(!$process.HasExited){ Write-Host "Killing process"; $process.kill(); $process.StandardOutput.ReadToEnd() } }
$process = New-SmartProcess 'nslookup'
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
$process.WriteLine('set q=MX')
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
$process.WriteLine('skadefro.dk')
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
if(!$process.HasExited){ $process.kill() }

You can find my SuperOffice PowerShell snapin inclucing Source code here