sexta-feira, 19 de dezembro de 2008

>> CRM 4.0 - AJAX + Preenchimento Automático de Lookup

Já tiveram a necessidade de preenchimento automático de um campo Lookup, baseado na escolha de alguma informação em outro campo? Somente em JavaScript?

Segue código template que ilustra isto, de forma que, ao selecionar uma Conta na Oportunidade, o sistema atualize a Lista de Preço da Oportunidade (existente na Conta).

Note que o código (JavaScript) utiliza o Web Services do CRM, método Retrieve para busca de dados de uma entidade, baseado no ID.

No [OnChange] do campo [Cliente Provável] da Oportunidade, adicionar o template abaixo:

P.S.: Substitua o nome [CAMPO_RETORNO] pelo nome do campo que representa o ID da Lista de Preço da Conta.

---- ONCHANGE FIELD ACTION ----
getAccountPriceLevel();

function getAccountPriceLevel()
{
var server = window.location.host;
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp.open("POST", "http://" + server + "/mscrmservices/2007/crmservice.asmx", true);
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Retrieve");

var soapBody = "<soap:Body><Retrieve xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\"><entityName>account</entityName>";
soapBody += "<id>" + crmForm.all.customerid.DataValue[0].id + "</id>";
soapBody += "<columnSet xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:ColumnSet\"><q1:Attributes>";
soapBody += "<q1:Attribute>CAMPO_RETORNO</q1:Attribute>";
soapBody += "</q1:Attributes></columnSet>";
soapBody += "</Retrieve></soap:Body>";

var soapXml = "<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'>";
soapXml += GenerateAuthenticationHeader();
soapXml += soapBody;
soapXml += "</soap:Envelope>";

xmlhttp.onreadystatechange = StateChangeAccountPriceLevel;
xmlhttp.send(soapXml);
}


function StateChangeAccountPriceLevel()
{
if (xmlhttp.readyState == 4)
{
var oNodes = xmlhttp.responseXML.selectSingleNode("//RetrieveResult").childNodes;
var priceLevelID = oNodes[0].text;

if (crmForm.all.pricelevelid.DataValue == null)
{
var lookupData = new Array();
var lookupItem= new Object();
lookupItem.id = priceLevelID;
lookupItem.typename = 'pricelevel';
lookupItem.name = 'Lista de Preço Padrão';
lookupData[0] = lookupItem;
crmForm.all.pricelevelid.DataValue = lookupData;
}
}
}

quinta-feira, 18 de dezembro de 2008

>> CRM 4.0 - Data Migration Manager

[Data Migration Manager] - desenvolvido para transporte de dados em massa de uma fonte de dados para a base do CRM.

Segue algumas orientações para uso do DMM:

1 - Demostração

Veja este "webcast" que ensina o uso do DMM - http://channel9.msdn.com/posts/jodonnell/Microsoft-Dynamics-CRM-40--Data-Migration-Manager-with-John-ODonnell/

2 - Restrições

- NÃO instale o DMM no Servidor do CRM (DMM só roda no [Windows Vista] ou [Windows XP]);
- Alguns desenvs instalam o DMM no Servidor do [SQL SERVER - 32 bits];

- Se o banco de dados for de [64 bits] o DMM Não vai funcionar;

- Use uma máquina Client, 32 bits com [local SQL Express] para o DataBase de Migração;
- Instale o DMM escolhendo a opção do banco de dados [SQL SERVER 2005 EXPRESS EDITION];

- Instale e rode o DMM com um usuário [local admin] e [CRM Admin]. Isto é importante porque o DMM usa o Web Services do CRM! Inclusive o DMM só pode ser usado pelo usuário que o instalou.

- DMM não pode ser usado enquanto o [Microsoft Office Outlook] estiver rodando;

Fonte adicional: http://rc.crm.dynamics.com/rc/regcont/en_us/Live/help/How_dmw_install.htm

terça-feira, 16 de dezembro de 2008

>> CRM 4.0 - "Limitações" no Acompanhamento de Atividades

Apesar da flexibilidade no uso de Acompanhamento de Atividades no CRM (http://gtezini.blogspot.com/2008/12/crm-40-boto-acompanhamento-de.html) existem algumas "limitações" que impedem o uso deste recurso.

Por exemplo, se você tentar efetuar um Acompanhamento (do tipo Tarefa) para uma atividade do tipo [Telefonema], atividade esta pertencente a uma [Campanha Rápida], ao salvar o acompanhamento, receberá a seguinte mensagem do CRM:

"[Campanhas rápidas] não podem ter [tarefa] como uma [atividade de acompanhamento]"

O que isto significa?

Seguindo o mesmo raciocínio do [KB 909824] que explica a impossibilidade de criação de [acompanhamento de atividade do tipo tarefa] para uma [atividade do tipo email]...

Podemos explicar este caso da seguinte forma:

"Quando você tenta criar um [acompanhamento do tipo tarefa], o Microsoft CRM configura o campo "Regarding" da [atividade de acompanhamento do tipo tarefa]. Para este campo, o Microsoft CRM tenta usar o valor do campo "Regarding" da [atividade de telefonema]. Contudo, não existe um relacionamento entre o [acompanhamento do tipo tarefa] e a [campanha rápida]."

Por este motivo aparece a msg de restrição.

Solução MS:

Use os seguintes tipos de atividades para acompanhamento:

- Telefonema;
- Carta;
- Fax;
- E-mail;
- Apontamento.

>> CRM 4.0 - Botão Acompanhamento de Atividades

Dica rápida...

Para que serve o botão de "Acompanhamento" existente nas telas de detalhes dos vários tipos de atividades do CRM?

O Acompanhamento (ou FollowUp) também é uma atividade no CRM, dos tipos tarefa, email, telefonema, etc. É um recurso que faz parte do Painel "Assistente de Formulário" do CRM.

Por exemplo, você pode criar uma atividade de serviço de acompanhamento futuro. Imagine uma empresa que acabou de concluir a troca de óleo para um cliente; você pode criar uma nova atividade de serviço para outra troca de óleo em seis meses, de forma a "associar" uma tarefa em outra.

Veja este exemplo: http://rc.crm.dynamics.com/rc/regcont/pt_br/op/articles/followup.aspx

sexta-feira, 12 de dezembro de 2008

>> CRM 4.0 - Revisar Quota Programaticamente

Existem alguns desenvolvimentos para o Dynamics CRM (via SDK) que poderíamos categorizar como sendo "não tão óbvios". Por exemplo, não existem exemplos completos para, programaticamente, Revisar uma Quota.

Juntando o "quebra-cabeça" para este caso, segue abaixo função Template.

public string ReviseQuote(string id)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";

CrmService CrmWebService = new CrmService();
CrmWebService.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
CrmWebService.CrmAuthenticationTokenValue = token;
CrmWebService.Credentials = System.Net.CredentialCache.DefaultCredentials;

string idRet = string.Empty;
Boolean CloseQuoteRequestOk = true;

// 1 - CLOSE QUOTE ACTIVITY

crmSdk.BusinessEntity beQuote = CrmWebService.Retrieve(crmSdk.EntityName.quote.ToString(), new Guid(id), new crmSdk.AllColumns());
crmSdk.quote _quote = beQuote as crmSdk.quote;

if (_quote == null) return string.Empty;

quoteclose _quoteClose = new quoteclose();
Lookup _lookUp = new Lookup();
_lookUp.type = EntityName.quote.ToString();
_lookUp.Value = new Guid(id);
CrmDateTime _crmDate1 = new CrmDateTime();
CrmDateTime _crmDate2 = new CrmDateTime();
_crmDate1.Value = new DateTime(1999, 12, 31, 23, 0, 0).ToString("u");
_crmDate2.Value = DateTime.Now.ToString("s");
_quoteClose.quoteid = _lookUp;
_quoteClose.subject = "Oferta Fechada (Revisada) " + _quote.quotenumber;
_quoteClose.quotenumber = _quote.quotenumber;
_quoteClose.revision = _quote.revisionnumber;
_quoteClose.actualstart = _crmDate1;
_quoteClose.actualend = _crmDate2;
CloseQuoteRequest closeQuoteRequest = new CloseQuoteRequest();
closeQuoteRequest.QuoteClose = _quoteClose;
closeQuoteRequest.Status = 7; // Revised
try
{
CrmWebService.Execute(closeQuoteRequest);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
CloseQuoteRequestOk = false;
System.Diagnostics.EventLog.WriteEntry("Application", "CloseQuoteRequest ERROR => QuoteID: [" + id + "] / Message: " + ex.Detail.InnerText);
}

if (!CloseQuoteRequestOk) return string.Empty;

// 2 - REVISE QUOTE
ReviseQuoteRequest reviseReq = new ReviseQuoteRequest();
ColumnSet columns = new ColumnSet();
columns.Attributes = new string[] { "quoteid" };
reviseReq.ColumnSet = columns;
reviseReq.QuoteId = new Guid(id);
reviseReq.ReturnDynamicEntities = false;
ReviseQuoteResponse reviseResp = null;
try
{
reviseResp = (ReviseQuoteResponse)CrmWebService.Execute(reviseReq);
idRet = "{" + (reviseResp.BusinessEntity as quote).quoteid.Value.ToString().ToUpper() + "}";
}
catch (System.Web.Services.Protocols.SoapException ex)
{
idRet = string.Empty;
System.Diagnostics.EventLog.WriteEntry("Application", "ReviseQuoteRequest ERROR => QuoteID: [" + id + "] / Message: " + ex.Detail.InnerText);
}
return idRet;
}

quarta-feira, 10 de dezembro de 2008

>> CRM 4.0 - Envio de Email Template (com anexos)

Segue código completo para envio de Email Template, com anexos.

A função SendEmailTemplate recebe como parâmetros o número, nome e ID da entidade como referência para o email (campo "referente a"), a descrição do Template do CRM a ser usado para envio do email, para quais usuários enviar e os anexos.


public static string SendEmailTemplate(int otc, string CrmEntityName, Guid oID, string templateTitle, string[] aUser, string[] aFiles)
{
// Set up the CRM Service.
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = "OrgName";

CrmService CrmWebService = new CrmService();
CrmWebService.Url = "http://localhost:5555/mscrmservices/2007/crmservice.asmx";
CrmWebService.CrmAuthenticationTokenValue = token;
CrmWebService.Credentials = System.Net.CredentialCache.DefaultCredentials;

Guid emailTemplateID = getTemplate(CrmWebService, otc, templateTitle);

if (emailTemplateID.Equals(Guid.Empty))
{
string err = "Não foi possível localizar o Email template ";
err += "com a palavra chave [" + templateTitle + "] para a entidade [" + otc.ToString() + "].";
return err;
}

crmSdk.WhoAmIRequest userRequest = new crmSdk.WhoAmIRequest();
crmSdk.WhoAmIResponse userResponse = (crmSdk.WhoAmIResponse)CrmWebService.Execute(userRequest);

crmSdk.BusinessEntity beSystemUser = CrmWebService.Retrieve(crmSdk.EntityName.systemuser.ToString(), userResponse.UserId, new crmSdk.AllColumns());
crmSdk.systemuser _systemuser = beSystemUser as crmSdk.systemuser;

if (_systemuser.internalemailaddress == null)
{
string err = "Seu usuário, no CRM, não possui EMAIL cadastrado (Email Primário).";
return err;
}

crmSdk.Owner _owner = new crmSdk.Owner();
_owner.type = crmSdk.EntityName.systemuser.ToString();
_owner.Value = userResponse.UserId;

// Instantiate the Template as an EMAIL Object.
crmSdk.InstantiateTemplateRequest instTemplate = new crmSdk.InstantiateTemplateRequest();
instTemplate.TemplateId = emailTemplateID;
instTemplate.ObjectId = oID;
instTemplate.ObjectType = CrmEntityName;
crmSdk.InstantiateTemplateResponse instTemplateResponse = null;

try
{
instTemplateResponse = (crmSdk.InstantiateTemplateResponse)CrmWebService.Execute(instTemplate);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "InstantiateTemplateResponse Error - " + ex.Detail.InnerText;
}
catch (Exception ex)
{
return "InstantiateTemplateResponse Error - " + ex.Message;
}

//Convert the returned entity to an email entity, this is really just the merged template body
crmSdk.email emailFromTemplate = (crmSdk.email)instTemplateResponse.BusinessEntityCollection.BusinessEntities[0];

//Add Recipients to the email
CrmUtil.crmSdk.activityparty partyFROM = new CrmUtil.crmSdk.activityparty();
partyFROM.partyid = new CrmUtil.crmSdk.Lookup();
partyFROM.partyid.type = CrmUtil.crmSdk.EntityName.systemuser.ToString();
partyFROM.partyid.Value = userResponse.UserId;

crmSdk.activityparty[] aParties = new crmSdk.activityparty[aUser.Length];

int loop = 0;

for (int j = 0; j < aUser.Length; j++)
{
aParties[loop] = new crmSdk.activityparty();
aParties[loop].partyid = new crmSdk.Lookup();
aParties[loop].partyid.type = crmSdk.EntityName.systemuser.ToString();
aParties[loop].partyid.Value = new Guid(aUser[j]);
loop++;
}

emailFromTemplate.from = new crmSdk.activityparty[] { partyFROM };
emailFromTemplate.to = aParties;
emailFromTemplate.ownerid = _owner;
emailFromTemplate.regardingobjectid = new crmSdk.Lookup();
emailFromTemplate.regardingobjectid.type = CrmEntityName;
emailFromTemplate.regardingobjectid.Value = oID;

// Create the Email
crmSdk.TargetCreateEmail targetCreate = new crmSdk.TargetCreateEmail();
targetCreate.Email = emailFromTemplate;
crmSdk.CreateRequest request = new crmSdk.CreateRequest();
request.Target = targetCreate;
crmSdk.CreateResponse response = null;
try
{
response = (crmSdk.CreateResponse)CrmWebService.Execute(request);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "TargetCreateEmail Error - " + ex.Detail.InnerText;
}

// Add Attachments
string msgRet = AddAttachments(response.id, aFiles);
if (!string.IsNullOrEmpty(msgRet)) return msgRet;

// Send Email
crmSdk.SendEmailRequest req = new crmSdk.SendEmailRequest();
req.EmailId = response.id;
req.TrackingToken = "";
req.IssueSend = true;
crmSdk.SendEmailResponse res = null;
try
{
res = (crmSdk.SendEmailResponse)CrmWebService.Execute(req);
}
catch (System.Web.Services.Protocols.SoapException ex)
{
return "SendEmailRequest - " + ex.Detail.InnerText;
}

return "Email criado com sucesso.";
}

private static Guid getTemplate(crmSdk.CrmService CrmWebService, int otc, string templateTitle)
{
crmSdk.QueryExpression _Query = new crmSdk.QueryExpression();
_Query.EntityName = crmSdk.EntityName.template.ToString();
_Query.ColumnSet = new crmSdk.AllColumns();

crmSdk.ConditionExpression _ce1 = new crmSdk.ConditionExpression();
crmSdk.ConditionExpression _ce2 = new crmSdk.ConditionExpression();

_ce1.AttributeName = "templatetypecode";
_ce1.Operator = crmSdk.ConditionOperator.Equal;
_ce1.Values = new object[] { otc };

_ce2.AttributeName = "title";
_ce2.Operator = crmSdk.ConditionOperator.Like;
_ce2.Values = new object[] { "%" + templateTitle + "%" };

crmSdk.FilterExpression _FilterExpression = new crmSdk.FilterExpression();
_FilterExpression.FilterOperator = crmSdk.LogicalOperator.And;
_FilterExpression.Conditions = new crmSdk.ConditionExpression[] { _ce1, _ce2 };

_Query.Criteria = _FilterExpression;
crmSdk.BusinessEntityCollection _Bec = CrmWebService.RetrieveMultiple(_Query);

Guid gResult = Guid.Empty;

if (_Bec.BusinessEntities.Length > 0)
{
crmSdk.template oTemplate = _Bec.BusinessEntities[0] as crmSdk.template;
gResult = oTemplate.templateid.Value;
}

return gResult;
}

private static string AddAttachments(crmSdk.CrmService CrmWebService, Guid emailID, string[] aFiles)
{
string retMsg = string.Empty;
string sServerPath = HttpContext.Current.Server.MapPath(@"\");
sServerPath = sServerPath.Replace(@"\", "||");

for (int i = 0; i < aFiles.Length; i++)
{
string fileNameCRM = aFiles[i];
RemoveSpecialChars(ref fileNameCRM);

string file = sServerPath + @"UploadFiles\\" + aFiles[i];
string Newfile = sServerPath + @"UploadFiles\\" + fileNameCRM;

file = file.Replace("||", "\\\\");
Newfile = Newfile.Replace("||", "\\\\");

string additionalError = "\nTentando adicionar anexo: " + Newfile;

FileInfo info = new FileInfo(file);
string sExtension = info.Extension;

try
{
string fileCompare1 = aFiles[i].Trim().ToLower();
string fileCompare2 = fileNameCRM.Trim().ToLower();
if (!fileCompare1.Equals(fileCompare2))
{
info.MoveTo(Newfile);
}
}
catch (IOException ex)
{
retMsg ="IOException (MoveTo) - " + ex.Message + additionalError;
return retMsg;
}
catch (Exception ex)
{
retMsg = "IOException (MoveTo) - " + ex.Message + additionalError;
return retMsg;
}

FileStream fl = File.OpenRead(Newfile);

byte[] byteData = new byte[fl.Length];
fl.Read(byteData, 0, (int)fl.Length);
fl.Close();

crmSdk.activitymimeattachment oMimeAttach = new crmSdk.activitymimeattachment();
oMimeAttach.activityid = new crmSdk.Lookup();
oMimeAttach.activityid.Value = emailID;
oMimeAttach.activityid.type = crmSdk.EntityName.email.ToString();
oMimeAttach.attachmentnumber = new crmSdk.CrmNumber();
oMimeAttach.attachmentnumber.Value = i;
oMimeAttach.filename = fileNameCRM;
oMimeAttach.body = Convert.ToBase64String(byteData, 0, byteData.Length);

try
{
CrmWebService.Create(oMimeAttach);
try { info.Delete(); }catch { }
}
catch (SoapException ex)
{
retMsg = "Activitymimeattachment Error - " + ex.Detail.InnerText + additionalError;
}
catch (Exception ex)
{
retMsg = "Activitymimeattachment Error - " + ex.Message + additionalError;
}

}
return retMsg;
}

private static string RemoveSpecialChars(ref string fileName)
{
fileName = Regex.Replace(fileName, @"[^\w\.@-]", "");
fileName = fileName.Replace("ª", "");
fileName = fileName.Replace("º", "");
return fileName;
}

>> CRM 4.0 - Converter System.DateTime para CRMDateTime

public static CrmDateTime ConvertToCRMDateTime(DateTime dateTime)
{
CrmDateTime crmDateTime = new CrmDateTime();
crmDateTime.date = dateTime.ToShortDateString();
crmDateTime.time = dateTime.ToShortTimeString();
TimeSpan offset = TimeZone.CurrentTimeZone.GetUtcOffset(dateTime);
string sOffset = string.Empty;
if (offset.Hours < 0)
{
sOffset = "-" + (offset.Hours * -1).ToString().PadLeft(2, '0');
}
else
{
sOffset = "+" + offset.Hours.ToString().PadLeft(2, '0');
}

sOffset += ":" + offset.Minutes.ToString().PadLeft(2, '0');
crmDateTime.Value = dateTime.ToString(string.Format("yyyy-MM-ddTHH:mm:ss{0}", sOffset));

return crmDateTime;
}

>> CRM 4.0 Plugin - Dicas no uso do IPluginExecutionContext

Segue algumas dicas no uso do parâmetro que é passado para nosso plugin, o IPluginExecutionContext.

1 - Detectar a Origem da execução do Plugin (Alteração ou Conversão de um registro em outro).

Por exemplo, para saber se a execução do seu Plugin tem como origem a Conversão de uma Quota em Pedido.

using System;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;

public class Main_Plugin: IPlugin
{
public void Execute(IPluginExecutionContext context)
{
Boolean IsConvertionMode = (context.CallerOrigin.GetType() == typeof(AsyncServiceOrigin) && context.Depth > 1);


2 - Função genérica para recuperar o ID do registro do CRM para os Eventos de CREATE, UPDATE e DELETE.

public static Guid GetIDParam(DynamicEntity de, IPluginExecutionContext context, string propertyName)
{
Guid ret = Guid.Empty;

if (context.MessageName.Equals("Create"))
ret = (Guid)context.OutputParameters[ParameterName.Id];

else if (context.MessageName.Equals("Update"))
ret = ((Key)de.Properties[propertyName]).Value;

else if (context.MessageName.Equals("Delete"))
{
Moniker mon = (Moniker)context.InputParameters.Properties["Target"];
ret = mon.Id;
}

return ret;
}

3 - Passando dados entre Plugins

Compartilhar dados entre Plugins fica fácil com uso da Collection SharedVariables.

Imagine um Plugin de Account, com os Eventos de PRE-Update e POST-Update configurados. Uma determinada ação deverá ser tomada no evento de POST-Update; porém a ação somente será executada se ANTES da alteração do Account o registro possuia um status específico para um campo de Picklist.

Isto é possível 'guardando' o valor do Picklist na Collection SharedVariables (no PRE-Update) e consultando esta collection no POST-Update.

Segue template de código:

public class AccountPreHandler : IPlugin
{
public void Execute(IPluginExecutionContext context)
{
context.SharedVariables["statusAcct"] = [Get Picklist Value];
}
}

public class AccountPostHandler : IPlugin
{
public void Execute(IPluginExecutionContext context)
{
if (context.SharedVariables.Contains("statusAcct"))
{
int statusAcct = (int)context.SharedVariables["statusAcct"];
if (statusAcct==1)
// Do something...
}
}
}

sexta-feira, 5 de dezembro de 2008

>> Exchange Server & Active Sync Mobile

Configurar o Pocket PC para sincronizar emails com o Exchange, a primeira vista, parece ser simples. Porém, na prática, não é!

Um colega de trabalho, na empresa que estou atuando no momento, estava com este problema. Pesquisando nos principais fóruns sobre o assunto (around the world!), notei que a dificuldade é geral, e não existe uma solução simples devido a própria natureza do processo.

Porém consegui uma solução para o caso dele, seguindo os seguintes passos:

1 - Habilitar acesso OWA no Exchange Server Manager;

2 - Habilitar acesso ao OWA para determinado usuário no AD;

3 - Windows HOSTS File

- No Exchange Server, editar o arquivo hosts em C:\windows\system32\drivers\etc;
- Adicionar Public IP e FQDN do Exchange server (Windows server name + domain name, NÃO o Internet domain);
>> 1.1.1.1 server.contoso.local

4 - Exchange Virtual Directory

Desabilitar a opção "forms-based authentication".

Abra o Exchange Manager. Expanda o nó [Administrative Groups], expanda o nó [first administrative group] e também o nó [Servers]. Expanda [Protocols], depois [HTTP]. Abaixo de [HTTP], clique com o botão direito do mouse em [Exchange Virtual Server], e escolha [Properties]. Clique na aba [Settings], e limpe o checkbox [Enable Forms Based Authentication] e clique em OK.

Fonte: http://support.microsoft.com/default.aspx/kb/817379

5 - IIS

- Habilitar Integrated Windows Authentication;
- Habilitar Basic Authentication (opcional);
- Habilitar Kerberos / NTLM Authentication.

DOS Command:

- Ir ao diretório de scripts:
cd Inetpub\Adminscripts

- Consultar o tipo de autenticação:
cscript adsutil.vbs get w3svc/1/root/NTAuthenticationProviders

- Configurar o tipo de autenticação NTLM:
cscript adsutil.vbs set w3svc/1/root/NTAuthenticationProviders "Negotiate,NTLM";

P.S.: w3svc/1/root/ - O número "1" representa "Default Web Site" no IIS.

Links úteis:

85010014 error - http://www.tech-archive.net/Archive/PocketPC/microsoft.public.pocketpc.activesync/2006-02/msg00293.html

Exchange ActiveSync Errors and Solutions - http://www.pocketpcfaq.com/faqs/activesync/exchange_errors.php

OMA in Exchange 2003 - http://www.petri.co.il/configure_oma.htm

quinta-feira, 4 de dezembro de 2008

>> CRM - Número Sequencial Aleatório em JavaScript

O CRM possui, em algumas entidades, a geração automática de números sequenciais, como por exemplo na entidade Quota, no formato [COT-12345-SER43R].

Pensando nisto, criei um processo para fazer o mesmo (com exceção do número ser aleatório e não sequencial) totalmente em JavaScript.

P.S.: A função generateGuid() pode também ser útil em outros casos pois simula a geração do tipo GUID do banco de dados Sql.

Segue código template:

if (crmForm.FormType==1) // GERAR CÓDIGO SOMENTE NO CREATE DO FORM DO CRM
{
var seq = generateSeqNumber();
var seqPosStart = seq.length-4;
seq = seq.substr(seqPosStart,4);

var guid = generateGuid();
var guidPosStart = guid.length-6;
guid = guid.substr(guidPosStart,6);

crmForm.all.new_numerochamado.DataValue = "CHA-" + seq + "-" + guid;
}

function generateSeqNumber()
{
tmToday = new Date();
return String(tmToday.getTime());
}

function generateGuid()
{
var result, i, j;
result = '';
for(j=0; j<32; j++)
{
if( j == 8 j == 12 j == 16 j == 20)
result = result + '-';
i = Math.floor(Math.random()*16).toString(16).toUpperCase();
result = result + i;
}
return result;
}

segunda-feira, 1 de dezembro de 2008

>> [CRM + IE + [MaxConnPerServer=2]] = PostBack Errors

Abri hoje um chamado na Microsoft para tentar solucionar um problema com o Dynamics CRM 4.0, referente a um estranho problema de travamento na conversão de Quota em Pedido.

O CRM até criava o pedido normalmente, porém travava o IE para atualizar os dados do Pedido criado.

Geramos um diagóstico completo do Server (CRM/SQL/IIS/AD) e nada de errado aparecia nos logs.

Então chegamos a conclusão que o problema estava no IE. Por padrão, o IE trabalha com no máximo 2 sessões de download. Porém, no caso do CRM, fica muito restrita esta configuração. Aumentamos para 10 conexões simultâneas e o problema foi resolvido.

Os passos para correção são:

Inicie o editor do Registry (Regedt32.exe).
Localize a seguinte chave:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings

No menu [Editar], aponte para [Novo] e clique em [DWORD Value], então adicione os seguintes valores de chave:

Value name: MaxConnectionsPer1_0Server
Value data: 10
Base: Decimal

Value Name: MaxConnectionsPerServer
Value data: 10
Base: Decimal

P.S.: A Microsoft recomenda baixar o número de conexões indicada no KB até que o erro volte a ocorrer.

Fonte: http://support.microsoft.com/kb/282402

>> Microsoft CRM Diagnostics Tool 4

Para uma análise detalhada do Servidor do CRM 4.0, segue dica da ferramenta de Diagnóstico que coleta uma grande quantidade de informações, sendo elas:

CRM Server Report
– System Information
– Environment Variables
– TCPIP Parameters Registry
– Boot.ini Content
– .Net Framework Registration
– CRM Services Status and Logon Info
– CRM Registry Keys
– CRM Installed Files
- %ProgramFiles%
- GAC
– Web Sites Bindings
– ApplPool Indentities
– CRM WebSite Authentication
– SQL Server Information
– CRM System Settings
– CRM AD Groups Information
– Organization and Deployment Informations

(retrieve by SDK or SQL queries)

- SRS Data Connector Report
– System Information
– Environment Variables
– SRS Data Connector Installed files
- %ProgramFiles%\CRM Data Conn
- %ProgramFiles%\ReportServer
– ReportServer Settings (WMI)
– Report Manager Settings (WMI)
– RSReportServer.config content
– RSSrvPolicy.config content
– SRSDataConnectorSetup.log
- CRM E-Mail Router Report
– System Information
– Environment Variables
– CRM E-Mail Router nstalled files
– CRM E-Mail Router Service Info
– Microsoft.Crm.Tools.EmailAgent.Xml content
– Crm40ExchangeSetup.log

Segue aparência da Ferramenta:



Link para Download: https://community.dynamics.com/crm/b/crmjimwang/archive/2010/03/08/download-58-crm-4-0-diagnostics-tool-alternative-link.aspx

Para habilitar o Trace manualmente: http://support.microsoft.com/kb/907490/en-us

quinta-feira, 27 de novembro de 2008

>> CRM 4.0 - Exclusão física de registros

O serviço de exclusão física de registros no CRM 4.0 mudou desde a versão anterior (3.0). O processo de exclusão é gerenciado pelo mesmo sistema que controla Triggers de Workflows e todos os jobs de processos assíncronos, como o Duplicate Detection e a chamada de plugins assíncronos (CrmAsyncService.exe). O tempo padrão é de 1440 minutos (24 hs) para o serviço de exclusão física de registros (tabela ScaleGroupOrganizationMaintenanceJobs do BD MSCRM_CONFIG). Segue link para baixar utilitário para poder mudar este tempo.

http://code.msdn.microsoft.com/ScaleGroupJobEditor/Release/ProjectReleases.aspx?ReleaseId=676

Se desejar "forçar" a exclusão física dos registros, podemos fazer isto por executar um Update na tabela de Jobs do CRM da seguinte forma:

USE MSCRM_CONFIG
UPDATE ScaleGroupOrganizationMaintenanceJobs
SET NextRunTime = getdate() -- Right Now!
WHERE OperationType = 14 -- Deletion Service

Após o Update, reinicie o serviço assíncrono do CRM.

>> Dicas JavaScript (ASP.NET & MSCRM 4.0)

Segue abaixo códigos JavaScript úteis no desenvolvimento ASP.NET e também para uso no Microsoft Dynamics CRM 4.0:

JS ASP.NET - Função para obter o valor de algum dos parâmetros da Query String da página.

function getQueryStringParam(name)
{
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null ) return "";
else
return results[1];
}

JS ASP.NET - Função para Converter o primeiro caractere de toda frase em Maiúsculo.

function UCWords(str)
{
var arrStr = str.split(" ");
var strOut = "";
var i = 0;
var stringValues = "da-de-do-das-dos";
while (i < arrStr.length)
{

if (stringValues.indexOf(arrStr[i].toLowerCase()) < 0)
{
firstChar = arrStr[i].substring(0,1);
remainChar = arrStr[i].substring(1);
firstChar = firstChar.toUpperCase();
remainChar = remainChar.toLowerCase();
strOut += firstChar + remainChar + ' ';
}
else
strOut += arrStr[i] + ' ';

i++;
}
return strOut.substr(0,strOut.length - 1);
}

JS ASP.NET - Adicionar / Remover itens em um DropDownList.

function ClearDropDownList(list)
{
for(i=list.options.length-1; i>=0 ;i--)
{
list.remove(i);
}
}
function addDropDownListOption(list, text, value)
{
var optn = document.createElement("OPTION");
optn.text = text;
optn.value = value;
list.options.add(optn);
}

JS MSCRM 4.0 - Validação de campo Data via JavaScript.

if (ValidateDate(crmForm.all.requestdeliveryby))
{
// Custom code...
}

function ValidateDate(field){
var checkstr = "0123456789";
var DateField = field;
var Datevalue = "";
var DateTemp = "";
var seperator = ".";
var leap = 0;
var err = 0;
var i;
err = 0;
var day = DateField.DataValue.getDate();
var month = (DateField.DataValue.getMonth()+1);
var year = DateField.DataValue.getYear();
DateValue = day + "/" + month + "/" + year;
/* Delete all chars except 0..9 */
for (i = 0; i < DateValue.length; i++) {
if (checkstr.indexOf(DateValue.substr(i,1)) >= 0) {
DateTemp = DateTemp + DateValue.substr(i,1);
}
}
DateValue = DateTemp;
/* Validation of month*/
if ((month < 1) || (month > 12)) {
err = 21;
}
/* Validation of day*/
if (day < 1) {
err = 22;
}
/* Validation leap-year / february / day */
if ((year % 4 == 0) || (year % 100 == 0) || (year % 400 == 0)) {
leap = 1;
}
if ((month == 2) && (leap == 1) && (day > 29)) {
err = 23;
}
if ((month == 2) && (leap != 1) && (day > 28)) {
err = 24;
}
/* Validation of other months */
if ((day > 31) && ((month == "01") || (month == "03") || (month == "05") || (month == "07") || (month == "08") || (month == "10") || (month == "12"))) {
err = 25;
}
if ((day > 30) && ((month == "04") || (month == "06") || (month == "09") || (month == "11"))) {
err = 26;
}
/* if 00 ist entered, no error, deleting the entry */
if ((day == 0) && (month == 0) && (year == 00)) {
err = 0; day = ""; month = ""; year = ""; seperator = "";
}
if (err != 0)
{
alert("Data Inválida!");
DateField.focus();
}
return (err == 0);
}

JS MSCRM 4.0 - Inserir / Formatar objetos dinamicamente na página do CRM.

with(crmForm.all.new_labelicms)
{
style.border = 0;
style.color = '#6699cc';
style.fonteSize = '7px';
style.fontWeight = 'bold';
style.overflow = 'hidden';
Disabled = true;
DataValue = 'Obs % ICMS: No caso de venda direta...'
}

/* Insert Adjacent Elements */
var server = window.location.host;
var oImg = document.createElement("<" + "img src='http://" + server + "/_imgs/ico/16_alert.gif'" + " />");
var oHr = document.createElement("<" + "HR class='ms-crm-MenuList-Spacer'" + ">");
var oSpn = document.createElement("<" + "span" + ">");
oSpn.innerText = ' Observações';
oSpn.style.fontWeight = 'bold';
crmForm.all.mit_labelicms.insertAdjacentElement("BeforeBegin",oImg);
crmForm.all.mit_labelicms.insertAdjacentElement("BeforeBegin",oSpn);
crmForm.all.mit_labelicms.insertAdjacentElement("BeforeBegin",oHr);

JS MSCRM 4.0 - AJAX no CRM - Chamada assíncrona em um Web Services Customizado.

function FillUOMProduct()
{
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
var server = window.location.host;
var params = "?productID=" + crmForm.all.productid.DataValue[0].id;
params += "&fields=productnumber,defaultuomid";
var url="http://"+ server+"/WebServicesCRM/Product.asmx/GetProductById"+params;
xmlHttpRequest.open("GET", url, true);
xmlHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlHttpRequest.setRequestHeader ("SOAPAction", "http://tempuri.org/GetProductById");
xmlHttpRequest.onreadystatechange = HandleStateChangeProduct;
xmlHttpRequest.send();
}

function HandleStateChangeProduct()
{
if (xmlHttpRequest.readyState == 4)
{
var doc = new ActiveXObject('Microsoft.XMLDOM');
var result = xmlHttpRequest.responseXML.xml;
doc.loadXML(result);
}
}

quarta-feira, 19 de novembro de 2008

>> CRM 4.0; IE6; KB953838; Error!!

Após efetuar um Security Update (KB953838) do Windows, teremos um repetitivo "File Download for Blank.aspx" no CRM 4.0.



Soluções:

1 - Instalar o IE7, ou:
2 - Desinstalar o KB953838, ou:
3 - Efetuar o download da Blank.aspx (qq diretório); right-click no arquivo -> Abrir como -> Escolher Programa -> selecionar IE, marcando o checkbox para sempre usar este tipo de programa;

>> Unable to Load Client Print Control - CRM 4.0

Recentemente, no cliente que estou atuando (com o Microsoft Dynamics CRM 4.0) houve uma reclamação de um erro ao tentar imprimir um relatório desenvolvido no Reporting Services.



Pesquisando sobre o assunto e a mensagem de erro descobri a causa, um HotFix de Segurança da Microsoft (KB956391).

Para solucionar o problema, vai ser necessário atualizações de Service Packs, do Report Viewer e do Sql Server (2005 / 2008).

Segue links para Download dos pacotes:

Microsoft Report Viewer Redistributable 2005 Service Pack 1
http://www.microsoft.com/downloads/details.aspx?FamilyID=82833f27-081d-4b72-83ef-2836360a904d&DisplayLang=en

Security Update for SQL Server 2005 Service Pack 3
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=ae7387c3-348c-4faa-8ae5-949fdfbe59c4

______________________________________________________________________

Microsoft Report Viewer Redistributable 2008 Service Pack 1
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=bb196d5d-76c2-4a0e-9458-267d22b6aac6

Security Update for SQL Server 2008 Service Pack 1
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=66ab3dbb-bf3e-4f46-9559-ccc6a4f9dc19


P.S.: Se o servidor do [Reporting Services] não for o mesmo do [Sql Server], existe a necessidade da instalação do [Microsoft Dynamics CRM Data Connector for Reporting Services] no servidor do Sql Server, para que a autenticação funcione corretamente na execução de relatórios pelo CRM.

terça-feira, 18 de novembro de 2008

>> ResizeControls - aspx

Gostaria de deixar os objetos de uma aspx sempre com a altura da janela do browser?

Adicione o código abaixo no html de sua página:

P.S.: Execute a função resizeControls() do método onresize do BODY da página.

>> Busca do ID de um Web Site no IIS

Segue código Template para encontrar o ID de um determinado projeto Web no IIS:

<< C# - VS.NET 2005 - Console Application >>

using System.DirectoryServices;
using System;

public class IISAdmin
{
public static void GetWebsiteID(string websiteName)
{
DirectoryEntry w3svc = new DirectoryEntry("IIS://localhost/w3svc");

foreach(DirectoryEntry de in w3svc.Children)
{
if(de.SchemaClassName == "IIsWebServer" && de.Properties["ServerComment"][0].ToString() == websiteName)
{
Console.Write(de.Name);
}

}

}
public static void Main()
{
GetWebsiteID("Default Web Site");
}

}

>> .NET 2.0 - Chamada Web Services - Sem "Add Web References"

Segue exemplo de código para chamada a Web Services sem a necessidade de adição da referência web no projeto.

Basta passar para a função "CallWebServices" a URL, o nome da função e os parâmetros do Web Services.

Exemplo de uso da função:

using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Text;

List<'Hashtable> oParams = new List<'Hashtable>();
Hashtable ht1 = new Hashtable();
ht1.Add("paramName", "productid");
ht1.Add("paramValue", "413D080B-09A7-DB11-89C7-0016356BE094");
oParams.Add(ht1);

string xml = CallWebServices("http://localhost/MyWS/product.asmx", "GetProductPriceList", oParams);

public static string CallWebServices(string url, string functionName, List<'Hashtable> oParams)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Credentials = System.Net.CredentialCache.DefaultCredentials;
request.Headers.Add("SOAPAction", "\"http://tempuri.org/" + functionName + "\"");
request.Method = "POST";
request.ContentType = "text/xml; charset=utf-8";
request.Accept = "text/xml";
request.Timeout = 10000;
Stream requestStream = request.GetRequestStream();
string soapEnvelope = "";
soapEnvelope += " soapEnvelope += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
soapEnvelope += " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"";
soapEnvelope += " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
soapEnvelope += " ";
soapEnvelope += " <" + functionName + " xmlns=\"http://tempuri.org/\">";
if (oParams.Count > 0)
{
Boolean hashListOk = (oParams[0].Contains("paramName") && oParams[0].Contains("paramValue"));
if (!hashListOk) return string.Empty;
foreach (Hashtable ht in oParams)
{
soapEnvelope += "<" + ht["paramName"].ToString() + ">" + ht["paramValue"].ToString() + "";
}
}
soapEnvelope += " ";
soapEnvelope += "
";
soapEnvelope += " ";
// Convert the string into a byte array.
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] ByteArray = encoder.GetBytes(soapEnvelope);
// Write data to the stream.
requestStream.Write(ByteArray, 0, ByteArray.Length);
requestStream.Flush();
requestStream.Close();
StreamReader esr = null;
string result = string.Empty;
try
{
esr = new StreamReader(request.GetResponse().GetResponseStream());
result = esr.ReadToEnd();
}
catch (System.Web.Services.Protocols.SoapException ex)
{
string x = ex.Detail.InnerText;
}
catch (Exception ex)
{
string x = ex.Message;
}
return result;
}

>> Artigos - ASP.NET 1.1

Olá pessoal,

Gostaria de compartilhar com vocês algumas dicas e códigos para facilitar no entendimento e desenvolvimento na plataforma .NET, mais especificamente no ASP.NET 1.1 (em breve também para as versões 2.0 e 3.5) .

Abaixo segue links de artigos que publiquei no site do msdn para desenvolvedores, o SharePedia (http://www.msdnbrasil.com.br/Sharepedia/).

P.S.: Para baixar os artigos, é necessária uma conta no Passport.NET ou Windows Live da Microsoft.

- Navegando entre Forms - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52345

- Mantendo informações de estado em uma aplicação Web - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52270

- Entendendo Transações - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52644

- Autenticação e autorização de usuários - segurança na sua aplicação Web - PARTE I - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52199

- Autenticação e autorização de usuários - segurança na sua aplicação Web - PARTE II - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52200

- Autenticação e autorização de usuários - segurança na sua aplicação Web - PARTE III - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52201

- WebApp - Trabalhando com objetos – Parte I - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52266

- WebApp - Trabalhando com objetos – Parte II - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52267

- Tratamento de erros/exceptions - ASP.NET - http://www.msdnbrasil.com.br/secure/sharepedia/download.aspx?id=52034