プログラマ38の日記

主にプログラムメモです。

Salesforce: Excel VBAからSalesforce API呼び出しと、TLS1.0無効化の件

SalesforceとはTLS1.0では通信できない

脆弱性のため、TLS1.0は無効となり、ブラウザの設定変更、APIを使ったアプリケーションの対応が必要となりました。

 

javaなどバージョンアップすることで対応できるものはいいのですが、VBAは対応が難しいなと思っています。というのも、VBAの通信はOSのバージョンで決められているからです。

ExcelからSalesforceのデータの取得・更新、もしくはメタデータの操作などができるととても便利なんですが、Windows7でTSL1.0の通信となってしまうことが難点になります。

 

VBAからSalesforceAPIを利用する

SalesforceAPIVBAでコールします。次がログインする時のサンプルコードです。

 

Option Explicit

Sub sampleSF()
  Dim loginxml As String
  '接続用のxml
  loginxml = _
"<?xml version=""1.0"" encoding=""UTF-8""?><env:Envelope  " & _
"  xmlns:env=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" " & _
"  xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"">  " & _
" <env:Body>  " & _
" <m:login xmlns:m=""urn:partner.soap.sforce.com"" xmlns:sobj=""urn:sobject.partner.soap.sforce.com"">   " & _
"  <m:username>sample@sample.user</m:username>   " & _
"  <m:password>samplepassword</m:password>  " & _
" </m:login> " & _
" </env:Body>  " & _
"</env:Envelope> "

Dim http As Object
'WinHttp.WinHttpRequest か MSXML2.XMLHTTP を使う
'Set http = CreateObject("WinHttp.WinHttpRequest.5.1")   
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "POST", "https://login.salesforce.com/services/Soap/u/38.0", False
http.SetRequestHeader "Content-Type", "text/xml; charset=utf-8"
http.SetRequestHeader "SOAPAction", "login"
http.Send loginxml

' 結果出力
MsgBox http.responseText

'XMLをパースする処理を作成する。
・・・・・
End Sub

Windows7だと、標準だとTLS1.0が指定されるのが問題

 上のサンプルは、Windows10では問題なく動作しますが、Windows7だと動作しません。Windows7の通信がTLS1.0だからです。

 

Windows7でTLS1.1以上にする指定はVBAのObjectごとに次の通りです。

WinHttp.WinHttpRequest.5.1:パッチをあて、レジストリを修正することで対応可能

MSXML2.XMLHTTP:対応方法無し

 

一括登録ツールとか、数式を駆使した入力フォームなんかVisualforceよりExcelの方が向いてるときが多いんですよね。でも、Windows7の壁が立ちはだかります。

2020年にWindows7のサポートが切れるのを気長に待ちますかね。。

 

でも、ここ最近Windows10を使うことが多くなってきたので気にせずにExcel VBAからSalesforce APIをコールするマクロを作成しています。

作成したのは次のExcelマクロになります。

  • ロール作成
  • カスタム項目作成(対応していないデータ型があります)
  • オブジェクト一覧と項目一覧の取得
  • カスタム表示ラベルの作成と一覧取得
  • explainの実行
  • AnonymousApexの実行
  • ナレッジのカテゴリ一覧の取得
  • レイアウト一覧とレイアウト項目の取得
  • プロファイルのIPアドレス制限一覧の取得
  • アプリケーション毎のタブ一覧の取得

[ロールの作成]

[カスタム項目の作成]

crmprogrammer38.hatenablog.com

[オブジェクト・項目の一覧取得]

crmprogrammer38.hatenablog.com

 


[カスタム表示ラベルの作成・一覧の取得]

crmprogrammer38.hatenablog.com

[explainの実行]

crmprogrammer38.hatenablog.com

[AnonymousApexの実行]

crmprogrammer38.hatenablog.com

[ナレッジのカテゴリ一覧の取得]

crmprogrammer38.hatenablog.com

[レイアウト一覧とレイアウト項目一覧の取得] 

crmprogrammer38.hatenablog.com

 [プロファイルのIPアドレス制限一覧の取得]

crmprogrammer38.hatenablog.com

crmprogrammer38.hatenablog.com

 

Salesforce: カスタムボタンで、WebServiceの戻り値をMsxml2.XMLHTTPでsendする際にはまったこと

カスタムボタンで、Msxml2.XMLHTTPを使って別のシステムと接続する際にはまったことのメモになります。

 

やりたかったことは、

1.カスタムボタンで、文字列を戻すカスタムWebServiceをコール。

2.戻り値で取得した文字列を、Msxml2.XMLHTTPのsendで別システムに送信。

3.送信した結果を画面表示(alert)。

です。

 

どこではまったかというと2のsendです。

なぜだかうまくいかなくて、取得した戻り値をtoString()して文字列にしたらsendできました。

//カスタムWebService
global class SampleWS {
webservice static String sample1(String param){
return 'returnvalue:' + param;
}
}
//カスタムボタンの処理
{!requireScript('/soap/ajax/39.0/connection.js')}
{!requireScript('/soap/ajax/39.0/apex.js')}

var ret = sforce.apex.execute('SampleWS' , 'sample1' , { param : 'パラメータ' } );

var xmlhttp = new ActiveXObject ("Msxml2.XMLHTTP");
xmlhttp.open('POST', 'http://xxxx.xxxx/xxxx/xxxx', false);
xmlhttp.send(ret); ← これだとエラー
xmlhttp.send(ret.toString()); ← これだとうまくいく

理由は明確にはわかりませんでしたがtoString()で動いたのでOKとしました。

 

Salesforce: コマンドラインのメタデータのエクスポートツールを作りました

メタデータを使うシーンは多いです。

1.開発時のバックアップ

2.デプロイ用のモジュール

3.モジュールリリース後の確認

 

上記のような場面で何かと必要となるメタデータですが、「Force.com IDE」や「Force.com Migration Tool」のメタデータダウンロード機能だと、少し使いづらさを感じてツールを作成しました。

下記からダウンロードできます。ソースコードも同梱していますので、どういうコードを書いているかを確認いただけます。※もちろんユーザ、パスワードを悪用はしていませんので確認いただければと思います。

 

メタデータダウンロードツールはこちら (APIVer46)

 

 ツールの概要は次です。

・Java8を利用します。ツールには含めていないので別途インストールが必要です。

・動作確認はWindows7, 8 ,10で実施しています。

 

ツールの使い方はWindowsのバッチファイル「metadatabakup.bat」を編集し、環境変数に接続情報や、通信設定を指定します。

その後、「metadatabakup.bat」を起動してください。

f:id:crmprogrammer38:20170515111120p:plain

環境変数
backup.dir

メタデータファイルを保存するパスのルート。

このパスの下に、「metadata_yyyyMMddHHmmss」のフォルダをツール側で作成してメタデータを出力します。

metadata.include

取得するメタデータを限定する場合に指定します。(任意)

「ApexClass, ApexComponent, ApexPage, ApexTrigger, CustomObject, ・・・」などメタデータAPIのlistMetadataで取得できるタイプで指定してください。「CustomField」など、listMetadataで取得できないタイプは指定できません。

下記のmetadata.excludeと一緒には指定できません。

metadata.exclude

除外するメタデータを限定する場合に指定します。(任意)

指定方法は、上記のmetadata.includeと同様です。

上記のmetadata.includeと一緒には指定できません。

sfdc.url

APIのログインURLを指定します。

https://test.salesforce.com/services/Soap/u/43.0

https://login.salesforce.com/services/Soap/u/43.0

になります。他には、インスタンスURLを直接指定する、マイドメインURLを直接指定するなどでしょうか。

sfdc.username ログインユーザ名
sfdc.password ログインパスワード。セキュリティトークンを使用する場合は、パスワード+セキュリティトークンの文字列となります。
http.proxyHost プロキシホスト。通信時にプロキシを使用する場合指定します。
http.proxyPort プロキシポート番号。通信時にプロキシを使用する場合指定します。
http.proxyUser プロキシユーザ。プロキシでユーザ認証する場合指定します。
http.proxyPassword プロキシパスワード。プロキシでユーザ認証する場合指定します。
http.auth.ntlm.domain NTLMドメイン。ntlmドメインを使用する場合指定します。
JAVACMD

javaのコマンドを指定します。javaは8以上のバージョンが必要です。

パスが通っていれば「java」のままで、パスが通っていない場合フルパスでjavaコマンドまでを指定します。

 

後書き

「Force.com IDE」だと、メタデータの量が多いと、メタデータの選択時点で時間がかかってしまうのと、そもそもGUIのため自動化できません。
「Force.com Migration Tool」は、package.xmlを書くのがハードルが高く使いづらさを感じて、このツールを作りました。
ソースコードもつけてますので、自由に改造して使ってもらえるとうれしいです。

 

修正履歴

2017/7/24 CustomFeedFilterのダウンロード時にエラーとなる事象の対応を行いました。具体的には、CustomFeedFilterはワイルドカード指定でリクエストするように変更しています。

 

 

 メタデータエクスポートの過去バージョンは次です。

API42

API41

API40

API39

 

Salesforce: Java8のバージョンを上げたらSalesforceIDEで通信エラーが発生した件

Salesforceへproxy host, proxy port, prox yuser, proxy passwordを指定して接続しています。
Java8のバージョンを上げたら次の接続エラーが出たので回避策のメモです。

 

[接続エラー内容]
Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 authenticationrequired"

f:id:crmprogrammer38:20170515103008p:plain

 

 

[回避策]
eclipse.iniに次のシステムプロパティを追加
「-Djdk.http.auth.tunneling.disabledSchemes=」

 

例 

-startup
plugins/org.eclipse.equinox.launcher_1.3.0.v20140415-2008.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.200.v20150204-1316
-product
org.eclipse.epp.package.java.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
256M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vm
javaw.exe
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Xms40m
-Xmx512m
-Djdk.http.auth.tunneling.disabledSchemes=

[経緯]
Java 8 Update 111 (8u111)から、HTTPSトンネリングのBasic認証の無効化がされておりそれが原因でした。
上の回避策のプロパティを指定することで無効化が解除されるようです。
(なので、Java 8 でも Update110まではこのエラーになりません)

プロキシ経由、その際にユーザとパスワードが必要な環境で発生する可能性があります。

 

[所感]
tls1.0無効化でjava8に上げたら、上げたら上げたで別の通信の問題が待ってるという現実でした。通信周りはほんとに色々な問題がありますね。。

Salesforce: ガバナ制限対策 static変数を使ってSOQLの回数を減らそう

Salesforceでは、1つのApex トランザクションでのSOQLの回数制限があります。

Salesforceのstatic変数は、スコープがトランザクションであることを利用してSOQLの発行回数を次の方法で少なくできます。


1.設定情報のオブジェクトへのSOQLを一度で済ます。
2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。


具体的には次です。

1.設定情報のオブジェクトへのSOQLを一度で済ます。

設定情報のオブジェクトはいくつかありますが、次をピックアップします。
(1)レコードタイプ(RecordType)
(2)公開グループやロール、キュー(Group)
(3)現在のユーザ情報(User)


(1)レコードタイプへのSOQLを1度で済ます場合
public class RecordTypeUtils {

  private static Map<String, RecordType> recTypeMap = new Map<String, RecordType>();
  
  static {
    List<RecordType> rectypelist = [  Select
                   Id
                  ,Name
                  ,DeveloperName
                  ,SobjectType
                  From
                   RecordType
                  where IsActive = true ];
                  
    for( RecordType rectype : rectypelist ){
      String key = rectype.SobjectType + ':' + rectype.DeveloperName;
      
      recTypeMap.put( key, rectype );
    }
  }
  
  public static RecordType getRecordType(String sobjectType , String developerName ){
  
    return recTypeMap.get( sobjectType + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもRecordTypeへのSOQLは1度です。

RecordType rectype = RecordTypeUtils.getRecordType('SampleObject__c' , 'SampleRecordType');

 レコードタイプIdをセットしたい時に利用できます。

 

 (2)公開グループやロール、キューへのSOQLを1度で済ます
public class GroupUtils {

  private static Map<String, Group> groupMap = new Map<String, Group>();
  
  static {
    List<Group> grouplist = [  
                  Select
                     Id
                    ,Name
                    ,DeveloperName
                    ,RelatedId
                    ,Type
                  From
                     Group
                ];
                  
    for( Group grp : grouplist ){
      String key = grp.Type + ':' + grp.DeveloperName;
      
      groupMap.put( key, grp );
    }
  }
  
  public static Group getGroup(String type , String developerName ){
  
    return groupMap.get( type + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもGroupへのSOQLは1度です。

Group grp = GroupUtils.getGroup('Queue','SampleQueue');

所有者項目に特定のキューをセットしたい時に利用できます。

 

(3) 現在のユーザ情報へのSOQLを1度で済ます場合
public class CurrentUserUtils {

  @TestVisible
  private static User curuser;

  public static User getCurrentUser(){
  
    if( curuser == null ){
      curuser = [Select
             Id
            ,Name
            ,UserRole.Name
            ,Profile.Name
            ,Contact.Name
            ,Contact.Account.Name
            ,Manager.Name
            ,Manager.email
          From
             User
          Where Id =:Userinfo.getUserId()];
    }
  
    return curuser;
  }
}

 使う時は次のようにします。何度使用してもUser(現在のユーザ)へのSOQLは1度です。

User currentUser = CurrentUserUtils.getCurrentUser();

 現在のユーザ情報を取得する際に利用できます。Apex内のstatic変数「curuser」にTestVisibleアノテーションをつけた意味は、テストクラスの中でユーザを切り替えてテストする際に、ユーザ切り替え時にnullでクリアするためです。

※テストクラス内でSystem.runAs()でユーザを変えてもstatic変数は初期化されません。

そのため、上の処理でcuruserをセットすると、ユーザを変えても動きが正しくならないので、System.runAs()でユーザを変えた後 curuserにnullをセットする必要があります。

 

2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。

検索条件とその結果をstatic変数に保持するようにします。

単純なIdでの検索の場合、次のようになります。

public class SOQLResultUtils  {

  private static Map<String, List<Account>> accCache = new Map<String, List<Account>>();
  
  public static List<Account> getAccount(Set<Id> accIdSet){
  
    if( accIdSet == null ){
      accIdSet = new Set<Id>();
    }
  
    String srchstr = String.valueOf( accIdSet );
  
     if( accCache.keySet().contains( srchstr ) ){
       return accCache.get(srchstr );
     } else {
     
      List<Account> result = [Select
                   Id
                  ,Type
                  ,Phone
                  ,Fax
                  ,Industry
                  From
                   Account
                 ] ;
       accCache.put( srchstr , result );
       return result;
     }
  }
}

Contactのトリガで使った時には次のようになります。検索するIdのSetが同じであれば、SOQLは一度ですみます。

Set<Id> accset = new Set<Id>();
for( Contact c : Trigger.New )
if(c.AccountId != null) accset.add(c.AccountId);
}
List<Account> acclist =
SOQLResultUtils.getAccount(accset);

 同じトリガが何回か動いてしまった場合でも、SOQLの回数は増えません。

 

最後に

トリガのforループの中でSOQLを発行している場合のSOQLの回数制限のエラーはわかりやすく対処しやすいと思います。

 

でも、実際は、そんな簡単なエラーではなく、1つの画面に仕様が集中していたり、1つのオブジェクトにトリガが集中していたりで、SOQLの回数がつみあがった結果、SOQLの回数制限のエラーが発生するケースが多いと思います。

性質が悪いのが、SOQLの回数制限のエラーはある程度開発が進んだ後に発覚するため大きくプログラムを改修すると後退テストの影響範囲が大きくなってしまいます。

 

そんな時に上記のやり方なら、プログラムの改修範囲は大きくないのと、同じようなSOQLなら1つにまとめてしまうことで、さらにSOQLの回数を減らせます。

 

Salesforce: apexのstatic変数と、javaのstatic変数の違いでとまどったこと

私はjavaのプログラムを書くことが多いのですが、javaのstatic変数とapexのstatic変数の違いにとまどいました。

 

javaのstatic変数は、Classで保持する変数で、例えばWebアプリケーションであれば、そのWebアプリケーションの中で1つの値を保持できて、複数のユーザで1つの値を共有できます。

※厳密にはClassloader別ですが、いったんおいておきます。

 

ですが、apexのstatic変数は、トランザクションを開始してから終了するまでが変数のスコープで、トランザクション単位に管理されるため、ユーザ毎の値になります。

 

以下、原文

A static variable is static only within the scope of the Apex transaction. It’s not static across the server or the entire organization. The value of a static variable persists within the context of a single transaction and is reset across transaction boundaries. For example, if an Apex DML request causes a trigger to fire multiple times, the static variables persist across these trigger invocations.

 

 

Salesforce: メモ & 添付ファイルの非公開のチェックボックスは本人に限定する場合に便利

メモ & 添付ファイルには、非公開のチェックがあります。

f:id:crmprogrammer38:20170414123249p:plain

この非公開にチェックを入れた後には、プロファイル>システム管理者権限の

・すべてのデータの参照

・すべてのデータの編集

の権限を持つ人は参照できますが、それを除くと、本人以外は見えなくなります。

共有ルールや階層を使用したアクセス制御とは無関係にこの制御がかかります。

 

本人というのは、所有者に指定した人になるので、システム管理者や、Apexプログラムなどからその人にだけ見せたいメモや、添付ファイルを作成することができます。

 

確実にその人だけが参照できる情報を登録する際には便利です。

 

下が、メモ & 添付ファイルをApexやAPIで使用する際の項目になります。

 

メモ

API名:Note

API ラベル タイプ
ParentId 参照先 ID reference
Title タイトル string
IsPrivate 非公開 boolean
Body 内容 textarea
OwnerId 所有者 ID reference

 

添付ファイル

API名:Attachment

API ラベル タイプ
ParentId 参照先 ID reference
Name ファイル名 string
IsPrivate 非公開 boolean
ContentType コンテンツタイプ string
Body 本文 base64
OwnerId 所有者 ID reference
Description 説明 textarea