diff --git a/DynamicsCRMProvider.Docs.sln b/DynamicsCRMProvider.Docs.sln index 345432c..3bf4653 100644 --- a/DynamicsCRMProvider.Docs.sln +++ b/DynamicsCRMProvider.Docs.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30110.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{D383A6BC-FD4A-4C9C-99CA-5AE5C8A7B261}" ProjectSection(SolutionItems) = preProject diff --git a/DynamicsCRMProvider.sln b/DynamicsCRMProvider.sln index a080c21..c5c0d17 100644 --- a/DynamicsCRMProvider.sln +++ b/DynamicsCRMProvider.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .travis.yml = .travis.yml appveyor.yml = appveyor.yml build.fsx = build.fsx + src\DynamicsCRMProvider.nuspec = src\DynamicsCRMProvider.nuspec paket.dependencies = paket.dependencies README.md = README.md RELEASE_NOTES.md = RELEASE_NOTES.md @@ -21,6 +22,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{D383A6BC-F docs\tools\templates\template.cshtml = docs\tools\templates\template.cshtml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Crm.Services.Utility", "src\Microsoft.Crm.Services.Utility\Microsoft.Crm.Services.Utility.csproj", "{53CF97C2-34BC-4580-B62E-9F12E7626120}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DynamicsCRMProvider.Runtime", "src\DynamicsCRMProvider.Runtime\DynamicsCRMProvider.Runtime.fsproj", "{B0AFF5DE-4330-4259-A0EF-860CE0712B90}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "DynamicsCRMProvider.DesignTime", "src\DynamicsCRMProvider.DesignTime\DynamicsCRMProvider.DesignTime.fsproj", "{C92A4D45-D30B-4148-93E6-E093B6BA10CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +38,18 @@ Global {4EEA1AD8-E8A9-4D54-BC39-D5F536366945}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EEA1AD8-E8A9-4D54-BC39-D5F536366945}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EEA1AD8-E8A9-4D54-BC39-D5F536366945}.Release|Any CPU.Build.0 = Release|Any CPU + {53CF97C2-34BC-4580-B62E-9F12E7626120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53CF97C2-34BC-4580-B62E-9F12E7626120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53CF97C2-34BC-4580-B62E-9F12E7626120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53CF97C2-34BC-4580-B62E-9F12E7626120}.Release|Any CPU.Build.0 = Release|Any CPU + {B0AFF5DE-4330-4259-A0EF-860CE0712B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0AFF5DE-4330-4259-A0EF-860CE0712B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0AFF5DE-4330-4259-A0EF-860CE0712B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0AFF5DE-4330-4259-A0EF-860CE0712B90}.Release|Any CPU.Build.0 = Release|Any CPU + {C92A4D45-D30B-4148-93E6-E093B6BA10CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C92A4D45-D30B-4148-93E6-E093B6BA10CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C92A4D45-D30B-4148-93E6-E093B6BA10CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C92A4D45-D30B-4148-93E6-E093B6BA10CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 5f41ff5..afc8bbe 100644 --- a/README.md +++ b/README.md @@ -22,5 +22,6 @@ This has been built against the Microsoft Dynamics CRM 2011 SDK Version 5.0.12 - [@forki](https://github.com/forki) - [@pezipink](https://github.com/pezipink) +- [@sergey-tihon](https://github.com/sergey-tihon) The default maintainer account for projects under "fsprojects" is [@fsgit](https://github.com/fsgit) - F# Community Project Incubation Space (repo management) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b9f0451..2ecb38c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,8 @@ -#### 0.1.1 - 05.01.2015 +#### 0.2.0 - 26.03.2015 * Migration to Paket -* Latest version of ProvidedTypes.fs -* Reference API +* Update to the latest version of ProvidedTypes.fs +* Added dependency to Microsoft.CrmSdk.CoreAssemblies +* Added Reference API -#### 0.1.0 - 26.01.2014 +#### 0.1.0 - 26.01.2014 * Ported DynamicsCRMProvider from FSharpx \ No newline at end of file diff --git a/build.fsx b/build.fsx index bc60dc4..74b913c 100644 --- a/build.fsx +++ b/build.fsx @@ -38,7 +38,7 @@ let summary = "A type provider for Microsoft Dynamics CRM 2011." let description = "A type provider for Microsoft Dynamics CRM 2011." // List of author names (for NuGet package) -let authors = [ "Ross McKinlay; Steffen Forkmann" ] +let authors = [ "Ross McKinlay; Steffen Forkmann; Sergey Tihon" ] // Tags for your project (for NuGet package) let tags = "F# fsharp typeproviders dynamics CRM" @@ -97,7 +97,7 @@ Target "AssemblyInfo" (fun _ -> |> Seq.iter (fun (projFileName, projectName, folderName, attributes) -> match projFileName with | Fsproj -> CreateFSharpAssemblyInfo (("src" @@ folderName) @@ "AssemblyInfo.fs") attributes - | Csproj -> CreateFSharpAssemblyInfo ((folderName @@ "Properties") @@ "AssemblyInfo.cs") attributes + | Csproj -> CreateCSharpAssemblyInfo ((folderName @@ "Properties") @@ "AssemblyInfo.cs") attributes | Vbproj -> CreateVisualBasicAssemblyInfo ((folderName @@ "My Project") @@ "AssemblyInfo.vb") attributes ) ) @@ -107,7 +107,7 @@ Target "AssemblyInfo" (fun _ -> // src folder to support multiple project outputs Target "CopyBinaries" (fun _ -> !! "src/**/*.??proj" - |> Seq.map (fun f -> ((System.IO.Path.GetDirectoryName f) @@ "bin/Release", "bin" @@ (System.IO.Path.GetFileNameWithoutExtension f))) + |> Seq.map (fun f -> ((System.IO.Path.GetDirectoryName f) @@ "bin/Release", "bin/DynamicsCRMProvider")) |> Seq.iter (fun (fromDir, toDir) -> CopyDir toDir fromDir (fun _ -> true)) ) @@ -177,16 +177,36 @@ Target "SourceLink" (fun _ -> // -------------------------------------------------------------------------------------- // Build a NuGet package +//Target "NuGet" (fun _ -> +// Paket.Pack(fun p -> +// { p with +// OutputPath = "bin" +// Version = release.NugetVersion +// ReleaseNotes = toLines release.Notes}) +//) + Target "NuGet" (fun _ -> - Paket.Pack(fun p -> + // Format the description to fit on a single line (remove \r\n and double-spaces) + let projectDescription = description.Replace("\r", "").Replace("\n", "").Replace(" ", " ") + NuGet (fun p -> { p with - OutputPath = "bin" + Authors = authors + Project = project + Summary = summary + Description = projectDescription Version = release.NugetVersion - ReleaseNotes = toLines release.Notes}) + ReleaseNotes = String.concat " " release.Notes + Tags = tags + OutputPath = "bin" + WorkingDir = "bin" + ToolPath = @"packages\NuGet.CommandLine\tools\NuGet.exe" + AccessKey = getBuildParamOrDefault "nugetkey" "" + Publish = hasBuildParam "nugetkey" }) + "src/DynamicsCRMProvider.nuspec" ) Target "PublishNuget" (fun _ -> - Paket.Push(fun p -> + Paket.Push(fun p -> { p with WorkingDir = "bin" }) ) diff --git a/docs/content/index.fsx b/docs/content/index.fsx index 878bf4f..982114f 100644 --- a/docs/content/index.fsx +++ b/docs/content/index.fsx @@ -26,10 +26,8 @@ Example This example demonstrates the use of the type provider from a F# script file: *) -// reference the type provider dll -#r "System.Runtime.Serialization" -#r "FSharp.Data.DynamicsCRMProvider.dll" -#r @"..\lib\Microsoft.Xrm.Sdk.dll" +// load the type provider dependencies +#load @"..\packages\DynamicsCRMProvider\DynamicsCRMProvider.fsx" open System open System.Linq diff --git a/paket.dependencies b/paket.dependencies index f0072f4..b816de2 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -5,7 +5,8 @@ nuget NUnit nuget NUnit.Runners nuget FAKE nuget SourceLink.Fake -nuget DynamicsCRMProvider +nuget NuGet.CommandLine +nuget Microsoft.CrmSdk.CoreAssemblies < 6.0.0 github fsharp/FAKE modules/Octokit/Octokit.fsx github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypes.fsi diff --git a/paket.lock b/paket.lock index 1f0e775..4009a2c 100644 --- a/paket.lock +++ b/paket.lock @@ -1,7 +1,6 @@ NUGET remote: https://nuget.org/api/v2 specs: - DynamicsCRMProvider (0.1.0) FAKE (3.18.0) FSharp.Compiler.Service (0.0.85) FSharp.Formatting (2.7.4) @@ -12,9 +11,13 @@ NUGET Microsoft.Bcl (1.1.10) Microsoft.Bcl.Build (>= 1.0.14) Microsoft.Bcl.Build (1.0.21) + Microsoft.CrmSdk.CoreAssemblies (5.0.18) + Microsoft.IdentityModel (>= 6.1.7600.16394) - framework: >= net40 + Microsoft.IdentityModel (6.1.7600.16394) - framework: >= net40 Microsoft.Net.Http (2.2.29) Microsoft.Bcl (>= 1.1.10) Microsoft.Bcl.Build (>= 1.0.14) + NuGet.CommandLine (2.8.3) NUnit (2.6.4) NUnit.Runners (2.6.4) Octokit (0.7.2) @@ -23,9 +26,9 @@ NUGET GITHUB remote: fsharp/FAKE specs: - modules/Octokit/Octokit.fsx (9c61ff79fce68a16fc4b027c8a931d89d7fb7c6d) + modules/Octokit/Octokit.fsx (12b839de599dd8ed448416a963025bb26b978a8d) Octokit remote: fsprojects/FSharp.TypeProviders.StarterPack specs: - src/ProvidedTypes.fs (04181b5a84801be79de4fd7dae3e9bf8d621d9e9) - src/ProvidedTypes.fsi (04181b5a84801be79de4fd7dae3e9bf8d621d9e9) \ No newline at end of file + src/ProvidedTypes.fs (cf96cf990590da7082197452b1b544e26ebed472) + src/ProvidedTypes.fsi (cf96cf990590da7082197452b1b544e26ebed472) \ No newline at end of file diff --git a/src/DynamicsCRMProvider/AssemblyInfo.fs b/src/Common/AssemblyInfo.fs similarity index 69% rename from src/DynamicsCRMProvider/AssemblyInfo.fs rename to src/Common/AssemblyInfo.fs index 3ffad52..69c576b 100644 --- a/src/DynamicsCRMProvider/AssemblyInfo.fs +++ b/src/Common/AssemblyInfo.fs @@ -4,9 +4,9 @@ open System.Reflection [] [] [] -[] -[] +[] +[] do () module internal AssemblyVersionInformation = - let [] Version = "0.1.1" + let [] Version = "0.2.0" diff --git a/src/DynamicsCRMProvider.DesignTime/DynamicsCRMProvider.DesignTime.fsproj b/src/DynamicsCRMProvider.DesignTime/DynamicsCRMProvider.DesignTime.fsproj new file mode 100644 index 0000000..2af03ed --- /dev/null +++ b/src/DynamicsCRMProvider.DesignTime/DynamicsCRMProvider.DesignTime.fsproj @@ -0,0 +1,162 @@ + + + + + Debug + AnyCPU + 2.0 + c92a4d45-d30b-4148-93e6-e093b6ba10cd + Library + DynamicsCRMProvider.DesignTime + FSharp.Data.DynamicsCRMProvider.DesignTime + v4.0 + 4.3.0.0 + DynamicsCRMProvider.DesignTime + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\FSharp.Data.DynamicsCRMProvider.DesignTime.XML + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\FSharp.Data.DynamicsCRMProvider.DesignTime.XML + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + True + paket-files/ProvidedTypes.fsi + + + True + paket-files/ProvidedTypes.fs + + + True + + + True + + + True + + + True + + + AssemblyInfo.fs + + + + + + + + Microsoft.Crm.Services.Utility + {53cf97c2-34bc-4580-b62e-9f12e7626120} + True + + + + True + + + + + + DynamicsCRMProvider.Runtime + {b0aff5de-4330-4259-a0ef-860ce0712b90} + True + + + + + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Crm.Sdk.Proxy.dll + True + True + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Xrm.Sdk.dll + True + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + + + + + + + ..\..\packages\Microsoft.IdentityModel\lib\net35\Microsoft.IdentityModel.dll + True + True + + + + + \ No newline at end of file diff --git a/src/DynamicsCRMProvider/TypeProviders.Helper.fs b/src/DynamicsCRMProvider.DesignTime/TypeProviders.Helper.fs similarity index 100% rename from src/DynamicsCRMProvider/TypeProviders.Helper.fs rename to src/DynamicsCRMProvider.DesignTime/TypeProviders.Helper.fs diff --git a/src/DynamicsCRMProvider/XrmDesignTime.fs b/src/DynamicsCRMProvider.DesignTime/XrmDesignTime.fs similarity index 97% rename from src/DynamicsCRMProvider/XrmDesignTime.fs rename to src/DynamicsCRMProvider.DesignTime/XrmDesignTime.fs index fa2249d..594d66f 100644 --- a/src/DynamicsCRMProvider/XrmDesignTime.fs +++ b/src/DynamicsCRMProvider.DesignTime/XrmDesignTime.fs @@ -5,6 +5,7 @@ namespace FSharp.Data.TypeProviders.XrmProvider +open FSharp.Data.TypeProviders.XrmProvider open FSharp.Data.TypeProviders.XrmProvider.Runtime open FSharp.Data.TypeProviders.XrmProvider.Runtime.Common @@ -26,40 +27,40 @@ open Microsoft.Xrm.Sdk.Client open Microsoft.Crm.Services.Utility open ProviderImplementation.ProvidedTypes - -/// Determines how relationship names appear on generated types -type RelationshipNamingType = - /// Relationships will be named with their schema name prefixed by 'Children of' or 'Parent of' and suffixed with the returned entity type name. - | ParentChildPrefix = 0 - /// Relationships will be named with their schema name prefixed by 1:N, N:1 or N:N. - | CrmStylePrefix = 1 - /// Relationships will be named only with their schema name. You will need to examine the intelliense comments to determine which direction the relationships point. - | SchemaNameOnly = 2 - -type OptionSetEnum = - | Unused = 2147483647 +open FSharp.Data.TypeProviders.XrmProvider.Internal type internal XrmRuntimeInfo (config : TypeProviderConfig) = - let runtimeAssembly = Assembly.LoadFrom(config.RuntimeAssembly) - member this.RuntimeAssembly = runtimeAssembly + let runtimeAssembly = Assembly.LoadFrom(config.RuntimeAssembly) + member this.RuntimeAssembly = runtimeAssembly type private RelationshipType = | OneToMany | ManyToOne | ManyToMany - + type private OptionSetType = | Picklist of PicklistAttributeMetadata - | State of StateAttributeMetadata - | Status of StatusAttributeMetadata + | State of StateAttributeMetadata + | Status of StatusAttributeMetadata + [] type XrmTypeProvider(config: TypeProviderConfig) as this = inherit TypeProviderForNamespaces() + static do + // When DynamicsCRMProvider is installed via NuGet, the Microsoft.Crm.* assembly + // will appear typically in "../../*/lib/net40". To support this, we look at + // FSharp.Data.DynamicsCRMProvider.dll.config which has this pattern in custom key "ProbingLocations". + // Here, we resolve assemblies by looking into the specified search paths. + Logging.logf "Registering Assembly Resolver..." + AppDomain.CurrentDomain.add_AssemblyResolve(fun source args -> + Logging.logf "Handling assembly resolve event..." + FSharp.Data.TypeProviders.XrmProvider.Internal.Configuration.resolveReferencedAssembly args.Name) + let xrmRuntimeInfo = XrmRuntimeInfo(config) let ns = "FSharp.Data.TypeProviders" let asm = Assembly.GetExecutingAssembly() - + let createOrgService uri clientCreds deviceCreds = let uri = Uri(uri) let orgProxy = new OrganizationServiceProxy(uri, null, clientCreds, deviceCreds); @@ -468,8 +469,4 @@ type XrmTypeProvider(config: TypeProviderConfig) as this = // add them to the namespace do this.AddNamespace(ns, [paramXrmType]) - -[] -do() - diff --git a/src/DynamicsCRMProvider.DesignTime/paket.references b/src/DynamicsCRMProvider.DesignTime/paket.references new file mode 100644 index 0000000..921cd53 --- /dev/null +++ b/src/DynamicsCRMProvider.DesignTime/paket.references @@ -0,0 +1,3 @@ +Microsoft.CrmSdk.CoreAssemblies +File:ProvidedTypes.fsi +File:ProvidedTypes.fs diff --git a/src/DynamicsCRMProvider.Runtime/App.config b/src/DynamicsCRMProvider.Runtime/App.config new file mode 100644 index 0000000..7252820 --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/DynamicsCRMProvider.Runtime/Configuration.fs b/src/DynamicsCRMProvider.Runtime/Configuration.fs new file mode 100644 index 0000000..3fd8eda --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/Configuration.fs @@ -0,0 +1,94 @@ +/// [omit] +module FSharp.Data.TypeProviders.XrmProvider.Internal.Configuration + +open System +open System.IO +open System.Reflection +open System.Configuration +open System.Collections.Generic + +/// Returns the Assembly object of FSharp.Data.DynamicsCRMProvider.dll +let getCRMProviderRuntimeAssembly() = + AppDomain.CurrentDomain.GetAssemblies() + |> Seq.find (fun a -> a.FullName.StartsWith("FSharp.Data.DynamicsCRMProvider.Runtime,")) + +/// Finds directories relative to 'dirs' using the specified 'patterns'. +/// Patterns is a string, such as "..\foo\*\bar" split by '\'. Standard +/// .NET libraries do not support "*", so we have to do it ourselves.. +let rec searchDirectories patterns dirs = + match patterns with + | [] -> dirs + | "*"::patterns -> + dirs |> List.collect (Directory.GetDirectories >> List.ofSeq) + |> searchDirectories patterns + | name::patterns -> + dirs |> List.map (fun d -> Path.Combine(d, name)) + |> searchDirectories patterns + +/// Returns the real assembly location - when shadow copying is enabled, this +/// returns the original assembly location (which may contain other files we need) +let getAssemblyLocation (assem:Assembly) = + if System.AppDomain.CurrentDomain.ShadowCopyFiles then + (new System.Uri(assem.EscapedCodeBase)).LocalPath + else assem.Location + +/// Reads the 'FSharp.Data.DynamicsCRMProvider.dll.config' file and gets the 'ProbingLocations' +/// parameter from the configuration file. Resolves the directories and returns +/// them as a list. +let getProbingLocations() = + try + let root = getCRMProviderRuntimeAssembly() |> getAssemblyLocation + let config = System.Configuration.ConfigurationManager.OpenExeConfiguration(root) + let pattern = config.AppSettings.Settings.["ProbingLocations"] + if pattern <> null then + [ let pattern = pattern.Value.Split(';', ',') |> List.ofSeq + for pat in pattern do + let roots = [ Path.GetDirectoryName(root) ] + for dir in roots |> searchDirectories (List.ofSeq (pat.Split('/','\\'))) do + if Directory.Exists(dir) then yield dir ] + else [] + with :? ConfigurationErrorsException | :? KeyNotFoundException -> [] + + +/// Given an assembly name, try to find it in either assemblies +/// loaded in the current AppDomain, or in one of the specified +/// probing directories. +let resolveReferencedAssembly (asmName:string) = + + // Do not interfere with loading FSharp.Core resources, see #97 + if asmName.StartsWith "FSharp.Core.resources" then null else + + // First, try to find the assembly in the currently loaded assemblies + let fullName = AssemblyName(asmName) + let loadedAsm = + System.AppDomain.CurrentDomain.GetAssemblies() + |> Seq.tryFind (fun a -> AssemblyName.ReferenceMatchesDefinition(fullName, a.GetName())) + match loadedAsm with + | Some asm -> asm + | None -> + + // Otherwise, search the probing locations for a DLL file + let libraryName = + let idx = asmName.IndexOf(',') + if idx > 0 then asmName.Substring(0, idx) else asmName + + let locations = getProbingLocations() + Logging.logf "Probing locations: %s" (String.concat ";" locations) + + let asm = locations |> Seq.tryPick (fun dir -> + let library = Path.Combine(dir, libraryName+".dll") + if File.Exists(library) then + Logging.logf "Found assembly, checking version! (%s)" library + // We do a ReflectionOnlyLoad so that we can check the version + let refAssem = Assembly.ReflectionOnlyLoadFrom(library) + // If it matches, we load the actual assembly + if refAssem.FullName = asmName then + Logging.logf "...version matches, returning!" + Some(Assembly.LoadFrom(library)) + else + Logging.logf "...version mismatch, skipping" + None + else None) + + if asm = None then Logging.logf "Assembly not found!" + defaultArg asm null \ No newline at end of file diff --git a/src/DynamicsCRMProvider.Runtime/DynamicsCRMProvider.Runtime.fsproj b/src/DynamicsCRMProvider.Runtime/DynamicsCRMProvider.Runtime.fsproj new file mode 100644 index 0000000..142dd81 --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/DynamicsCRMProvider.Runtime.fsproj @@ -0,0 +1,158 @@ + + + + + Debug + AnyCPU + 2.0 + b0aff5de-4330-4259-a0ef-860ce0712b90 + Library + DynamicsCRMProvider.Runtime + FSharp.Data.DynamicsCRMProvider.Runtime + v4.0 + 4.3.0.0 + DynamicsCRMProvider.Runtime + + + + true + full + false + false + bin\Debug\ + DEBUG;TRACE + 3 + bin\Debug\FSharp.Data.DynamicsCRMProvider.Runtime.XML + + + pdbonly + true + true + bin\Release\ + TRACE + 3 + bin\Release\FSharp.Data.DynamicsCRMProvider.Runtime.XML + + + 11 + + + + + $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets + + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets + + + + + + + + True + + + True + + + True + + + True + + + AssemblyInfo.fs + + + + + + + + + + + + + + + + True + + + + + + + Microsoft.Crm.Services.Utility + {53cf97c2-34bc-4580-b62e-9f12e7626120} + True + + + + + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Crm.Sdk.Proxy.dll + True + True + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Xrm.Sdk.dll + True + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + + + + + + + ..\..\packages\Microsoft.IdentityModel\lib\net35\Microsoft.IdentityModel.dll + True + True + + + + + \ No newline at end of file diff --git a/src/DynamicsCRMProvider.Runtime/Logging.fs b/src/DynamicsCRMProvider.Runtime/Logging.fs new file mode 100644 index 0000000..241891c --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/Logging.fs @@ -0,0 +1,45 @@ +/// [omit] +module FSharp.Data.TypeProviders.XrmProvider.Internal.Logging + +open System +open System.IO +open System.Diagnostics +open Microsoft.FSharp.Reflection + +/// The logging is enabled by setting the CRMPROVIDER_LOG environment variable +/// Alternatively, just change this constant to 'true' and logs will be +/// saved in the default location (see below) +let private loggingEnabled = + true + //System.Environment.GetEnvironmentVariable("CRM_PROVIDER_LOG") <> null + +/// Log file - if the CRM_PROVIDER_LOG variable is not set, the default on +/// Windows is "C:\Users\\AppData\Roaming\CrmProviderLog\log.txt" and on Mac +/// this is in "/User//.config/CrmProviderLog/log.txt") +let private logFile = + try + let var = System.Environment.GetEnvironmentVariable("CRM_PROVIDER_LOG") + if var <> null then var else + let appd = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + if not (Directory.Exists(appd + "/CrmProviderLog")) then Directory.CreateDirectory(appd + "/CrmProviderLog") |> ignore + appd + "/CrmProviderLog/log.txt" + with _ -> (* Silently ignoring logging errors *) null + +/// Append string to a log file +let private writeString str = + try + // This serializes all writes to the log file (from multiple processes) + use fs = new FileStream(logFile, FileMode.Append, Security.AccessControl.FileSystemRights.AppendData, FileShare.Write, 4096, FileOptions.None) + use writer = new StreamWriter(fs) + writer.AutoFlush <- true + + let pid = Process.GetCurrentProcess().Id + let tid = System.Threading.Thread.CurrentThread.ManagedThreadId + let apid = System.AppDomain.CurrentDomain.Id + writer.WriteLine(sprintf "[%s] [Pid:%d, Tid:%d, Apid:%d] %s" (System.DateTime.Now.ToString("G")) pid tid apid str) + with _ -> (*silently ignoring logging errors*) () + +/// Log formatted string to a log file +let logf fmt = + let f = if loggingEnabled then writeString else ignore + Printf.kprintf f fmt \ No newline at end of file diff --git a/src/DynamicsCRMProvider/Operators.fs b/src/DynamicsCRMProvider.Runtime/Operators.fs similarity index 100% rename from src/DynamicsCRMProvider/Operators.fs rename to src/DynamicsCRMProvider.Runtime/Operators.fs diff --git a/src/DynamicsCRMProvider.Runtime/XrmDesignTime.fs b/src/DynamicsCRMProvider.Runtime/XrmDesignTime.fs new file mode 100644 index 0000000..a4e46f1 --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/XrmDesignTime.fs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation 2005-2013. +// This sample code is provided "as is" without warranty of any kind. +// We disclaim all warranties, either express or implied, including the +// warranties of merchantability and fitness for a particular purpose. + +namespace FSharp.Data.TypeProviders.XrmProvider + +open System.Reflection +open Microsoft.FSharp.Core.CompilerServices + +open Microsoft.Xrm.Sdk.Metadata + +/// Determines how relationship names appear on generated types +type RelationshipNamingType = + /// Relationships will be named with their schema name prefixed by 'Children of' or 'Parent of' and suffixed with the returned entity type name. + | ParentChildPrefix = 0 + /// Relationships will be named with their schema name prefixed by 1:N, N:1 or N:N. + | CrmStylePrefix = 1 + /// Relationships will be named only with their schema name. You will need to examine the intelliense comments to determine which direction the relationships point. + | SchemaNameOnly = 2 + +type OptionSetEnum = + | Unused = 2147483647 \ No newline at end of file diff --git a/src/DynamicsCRMProvider/XrmRuntim.QueryExpressions.fs b/src/DynamicsCRMProvider.Runtime/XrmRuntim.QueryExpressions.fs similarity index 100% rename from src/DynamicsCRMProvider/XrmRuntim.QueryExpressions.fs rename to src/DynamicsCRMProvider.Runtime/XrmRuntim.QueryExpressions.fs diff --git a/src/DynamicsCRMProvider/XrmRuntime.Common.fs b/src/DynamicsCRMProvider.Runtime/XrmRuntime.Common.fs similarity index 100% rename from src/DynamicsCRMProvider/XrmRuntime.Common.fs rename to src/DynamicsCRMProvider.Runtime/XrmRuntime.Common.fs diff --git a/src/DynamicsCRMProvider/XrmRuntime.Linq.fs b/src/DynamicsCRMProvider.Runtime/XrmRuntime.Linq.fs similarity index 100% rename from src/DynamicsCRMProvider/XrmRuntime.Linq.fs rename to src/DynamicsCRMProvider.Runtime/XrmRuntime.Linq.fs diff --git a/src/DynamicsCRMProvider/XrmRuntime.Patterns.fs b/src/DynamicsCRMProvider.Runtime/XrmRuntime.Patterns.fs similarity index 100% rename from src/DynamicsCRMProvider/XrmRuntime.Patterns.fs rename to src/DynamicsCRMProvider.Runtime/XrmRuntime.Patterns.fs diff --git a/src/DynamicsCRMProvider.Runtime/paket.references b/src/DynamicsCRMProvider.Runtime/paket.references new file mode 100644 index 0000000..13fe296 --- /dev/null +++ b/src/DynamicsCRMProvider.Runtime/paket.references @@ -0,0 +1 @@ +Microsoft.CrmSdk.CoreAssemblies diff --git a/src/DynamicsCRMProvider.nuspec b/src/DynamicsCRMProvider.nuspec new file mode 100644 index 0000000..7f324a8 --- /dev/null +++ b/src/DynamicsCRMProvider.nuspec @@ -0,0 +1,38 @@ + + + + @project@ + @build.number@ + @authors@ + @authors@ + http://fsprojects.github.io/DynamicsCRMProvider/ + http://github.com/fsprojects/DynamicsCRMProvider/blob/master/LICENSE.txt + https://raw.githubusercontent.com/fsprojects/DynamicsCRMProvider/master/docs/files/img/logo.png + @summary@ + @description@ + @releaseNotes@ + Copyright 2015 + @tags@ + en-US + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DynamicsCRMProvider/DynamicsCRMProvider.fsproj b/src/DynamicsCRMProvider/DynamicsCRMProvider.fsproj index 6b4f645..74d9864 100644 --- a/src/DynamicsCRMProvider/DynamicsCRMProvider.fsproj +++ b/src/DynamicsCRMProvider/DynamicsCRMProvider.fsproj @@ -29,8 +29,10 @@ Program - C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe - C:\Code\DynamicsCRMProvider\DynamicsCRMProvider.Docs.sln + D:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe + d:\Personal\GitHub\DynamicsCRMProvider\DynamicsCRMProvider.Docs.sln + + pdbonly @@ -40,6 +42,10 @@ TRACE 3 bin\Release\FSharp.Data.DynamicsCRMProvider.XML + Program + D:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe + D:\Personal\GitHub\DynamicsCRMProvider\ + DynamicsCRMProvider.Docs.sln 11 @@ -52,42 +58,34 @@ - - True - paket-files/ProvidedTypes.fsi - - - True - paket-files/ProvidedTypes.fs + + AssemblyInfo.fs - - - - - - - - - + + + Always + False - - ..\..\packages\DynamicsCRMProvider\lib\net40\Microsoft.Crm.Services.Utility.dll - - - ..\..\packages\DynamicsCRMProvider\lib\net40\microsoft.xrm.sdk.dll - + + + + DynamicsCRMProvider.Runtime + {b0aff5de-4330-4259-a0ef-860ce0712b90} + True + + + + + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Crm.Sdk.Proxy.dll + True + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + + + + + + + ..\..\packages\Microsoft.IdentityModel\lib\net35\Microsoft.IdentityModel.dll + True + True + + + + \ No newline at end of file diff --git a/src/DynamicsCRMProvider/DynamicsCRMProvider.fsx b/src/DynamicsCRMProvider/DynamicsCRMProvider.fsx new file mode 100644 index 0000000..a413a5a --- /dev/null +++ b/src/DynamicsCRMProvider/DynamicsCRMProvider.fsx @@ -0,0 +1,28 @@ +#nowarn "211" +// Standard NuGet or Paket location +#I "." +#I "lib/net40" + +// Standard NuGet locations for CrmSdk +#I "../Microsoft.CrmSdk.CoreAssemblies.5.0.18/lib/net40" +#I "../Microsoft.IdentityModel.6.1.7600.16394/lib/net35" + +// Standard Paket locations for CrmSdk +#I "../Microsoft.CrmSdk.CoreAssemblies/lib/net40" +#I "../Microsoft.IdentityModel/lib/net35" + +// Try various folders that people might like +#I "bin" +#I "../bin" +#I "../../bin" +#I "lib" + +// Reference DynamicsCRMProvider and CrmSdk +#r "System.Web.dll" +#r "System.Runtime.Serialization.dll" +#r "Microsoft.IdentityModel.dll" +#r "Microsoft.Xrm.Sdk.dll" +#r "Microsoft.Crm.Sdk.Proxy.dll" +#r "Microsoft.Crm.Services.Utility.dll" +#r "FSharp.Data.DynamicsCRMProvider.dll" +#r "FSharp.Data.DynamicsCRMProvider.Runtime.dll" \ No newline at end of file diff --git a/src/DynamicsCRMProvider/DynamicsCRMProviderAttribute.fs b/src/DynamicsCRMProvider/DynamicsCRMProviderAttribute.fs new file mode 100644 index 0000000..470bdad --- /dev/null +++ b/src/DynamicsCRMProvider/DynamicsCRMProviderAttribute.fs @@ -0,0 +1,6 @@ +namespace FSharp.Data.TypeProviders.XrmProvider + +open Microsoft.FSharp.Core.CompilerServices + +[] +do() \ No newline at end of file diff --git a/src/DynamicsCRMProvider/paket.references b/src/DynamicsCRMProvider/paket.references deleted file mode 100644 index cb34510..0000000 --- a/src/DynamicsCRMProvider/paket.references +++ /dev/null @@ -1,2 +0,0 @@ -File:ProvidedTypes.fsi -File:ProvidedTypes.fs \ No newline at end of file diff --git a/src/DynamicsCRMProvider/paket.template b/src/DynamicsCRMProvider/paket.template index d42347f..4480cfe 100644 --- a/src/DynamicsCRMProvider/paket.template +++ b/src/DynamicsCRMProvider/paket.template @@ -1,4 +1,4 @@ -type project +type file id DynamicsCRMProvider owners Ross McKinlay, Steffen Forkmann @@ -20,9 +20,8 @@ summary Type providers for Dynamics CRM access. description Type providers for Dynamics CRM access. +dependencies + Microsoft.CrmSdk.CoreAssemblies >= 5.0.18 < 6.0 files - ../../bin/DynamicsCRMProvider/FSharp.Data.DynamicsCRMProvider.pdb ==> lib/net40 - ../../bin/DynamicsCRMProvider/FSharp.Data.DynamicsCRMProvider.xml ==> lib/net40 - ../../bin/DynamicsCRMProvider/Microsoft.Crm.Services.Utility.dll ==> lib/net40 - ../../bin/DynamicsCRMProvider/microsoft.xrm.sdk.xml ==> lib/net40 - ../../bin/DynamicsCRMProvider/microsoft.xrm.sdk.dll ==> lib/net40 \ No newline at end of file + ../../bin/DynamicsCRMProvider/FSharp.Data.DynamicsCRMProvider*.* ==> lib/net40 + ../../bin/DynamicsCRMProvider/Microsoft.Crm.Services.Utility.* ==> lib/net40 diff --git a/src/Microsoft.Crm.Services.Utility/Microsoft.Crm.Services.Utility.csproj b/src/Microsoft.Crm.Services.Utility/Microsoft.Crm.Services.Utility.csproj new file mode 100644 index 0000000..84f7dad --- /dev/null +++ b/src/Microsoft.Crm.Services.Utility/Microsoft.Crm.Services.Utility.csproj @@ -0,0 +1,121 @@ + + + + + Debug + AnyCPU + {53CF97C2-34BC-4580-B62E-9F12E7626120} + Library + Properties + Microsoft.Crm.Services.Utility + Microsoft.Crm.Services.Utility + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Microsoft.Crm.Services.Utility.XML + + + + True + + + True + + + True + + + True + + + + + + + + + + + + + + + + + + + + + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Crm.Sdk.Proxy.dll + True + True + + + ..\..\packages\Microsoft.CrmSdk.CoreAssemblies\lib\net40\Microsoft.Xrm.Sdk.dll + True + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + + + + + + + ..\..\packages\Microsoft.IdentityModel\lib\net35\Microsoft.IdentityModel.dll + True + True + + + + + \ No newline at end of file diff --git a/src/Microsoft.Crm.Services.Utility/Properties/AssemblyInfo.cs b/src/Microsoft.Crm.Services.Utility/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e3fce41 --- /dev/null +++ b/src/Microsoft.Crm.Services.Utility/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +// +using System.Reflection; + +[assembly: AssemblyTitleAttribute("Microsoft.Crm.Services.Utility")] +[assembly: AssemblyProductAttribute("DynamicsCRMProvider")] +[assembly: AssemblyDescriptionAttribute("A type provider for Microsoft Dynamics CRM 2011.")] +[assembly: AssemblyVersionAttribute("0.2.0")] +[assembly: AssemblyFileVersionAttribute("0.2.0")] +namespace System { + internal static class AssemblyVersionInformation { + internal const string Version = "0.2.0"; + } +} diff --git a/src/Microsoft.Crm.Services.Utility/deviceidmanager.cs b/src/Microsoft.Crm.Services.Utility/deviceidmanager.cs new file mode 100644 index 0000000..d70dc8c --- /dev/null +++ b/src/Microsoft.Crm.Services.Utility/deviceidmanager.cs @@ -0,0 +1,995 @@ +// ===================================================================== +// +// This file is part of the Microsoft Dynamics CRM SDK code samples. +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// +// This source code is intended only as a supplement to Microsoft +// Development Tools and/or on-line documentation. See these other +// materials for detailed information regarding Microsoft code samples. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// ===================================================================== +// +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Net; +using System.Runtime.Serialization; +using System.Security.Cryptography; +using System.ServiceModel.Description; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +namespace Microsoft.Crm.Services.Utility +{ + /// + /// Management utility for the Device Id + /// + public static class DeviceIdManager + { + #region Fields + private static readonly Random RandomInstance = new Random(); + + public const int MaxDeviceNameLength = 24; + public const int MaxDevicePasswordLength = 24; + #endregion + + #region Constructor + static DeviceIdManager() + { + PersistToFile = true; + } + #endregion + + #region Properties + /// + /// Indicates whether the registered device credentials should be persisted to the database + /// + public static bool PersistToFile { get; set; } + + /// + /// Indicates that the credentials should be persisted to the disk if registration fails with DeviceAlreadyExists. + /// + /// + /// If the device already exists, there is a possibility that the credentials are the same as the current credentials that + /// are being registered. This is especially true in automated environments where the same credentials are used continually (to avoid + /// registering spurious device credentials. + /// + public static bool PersistIfDeviceAlreadyExists { get; set; } + #endregion + + #region Methods + /// + /// Loads the device credentials (if they exist). + /// + /// + public static ClientCredentials LoadOrRegisterDevice() + { + return LoadOrRegisterDevice(null); + } + + /// + /// Loads the device credentials (if they exist). + /// + /// Device name that should be registered + /// Device password that should be registered + public static ClientCredentials LoadOrRegisterDevice(string deviceName, string devicePassword) + { + return LoadOrRegisterDevice(null, deviceName, devicePassword); + } + + /// + /// Loads the device credentials (if they exist). + /// + /// URL for the current token issuer + /// + /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. + /// + public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri) + { + return LoadOrRegisterDevice(issuerUri, null, null); + } + + /// + /// Loads the device credentials (if they exist). + /// + /// URL for the current token issuer + /// Device name that should be registered + /// Device password that should be registered + /// + /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. + /// + public static ClientCredentials LoadOrRegisterDevice(Uri issuerUri, string deviceName, string devicePassword) + { + ClientCredentials credentials = LoadDeviceCredentials(issuerUri); + if (null == credentials) + { + credentials = RegisterDevice(Guid.NewGuid(), issuerUri, deviceName, devicePassword); + } + + return credentials; + } + + /// + /// Registers the given device with Microsoft account with a random application ID + /// + /// ClientCredentials that were registered + public static ClientCredentials RegisterDevice() + { + return RegisterDevice(Guid.NewGuid()); + } + + /// + /// Registers the given device with Microsoft account + /// + /// ID for the application + /// ClientCredentials that were registered + public static ClientCredentials RegisterDevice(Guid applicationId) + { + return RegisterDevice(applicationId, (Uri)null); + } + + /// + /// Registers the given device with Microsoft account + /// + /// ID for the application + /// URL for the current token issuer + /// ClientCredentials that were registered + /// + /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. + /// + public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri) + { + return RegisterDevice(applicationId, issuerUri, null, null); + } + + /// + /// Registers the given device with Microsoft account + /// + /// ID for the application + /// Device name that should be registered + /// Device password that should be registered + /// ClientCredentials that were registered + public static ClientCredentials RegisterDevice(Guid applicationId, string deviceName, string devicePassword) + { + return RegisterDevice(applicationId, (Uri)null, deviceName, devicePassword); + } + + /// + /// Registers the given device with Microsoft account + /// + /// ID for the application + /// URL for the current token issuer + /// Device name that should be registered + /// Device password that should be registered + /// ClientCredentials that were registered + /// + /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. + /// + public static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, string deviceName, string devicePassword) + { + if (string.IsNullOrEmpty(deviceName) && !PersistToFile) + { + throw new ArgumentNullException("deviceName", "If PersistToFile is false, then deviceName must be specified."); + } + else if (string.IsNullOrEmpty(deviceName) != string.IsNullOrEmpty(devicePassword)) + { + throw new ArgumentNullException("deviceName", "Either deviceName/devicePassword should both be specified or they should be null."); + } + + LiveDevice device = GenerateDevice(deviceName, devicePassword); + return RegisterDevice(applicationId, issuerUri, device); + } + + /// + /// Loads the device's credentials from the file system + /// + /// Device Credentials (if set) or null + public static ClientCredentials LoadDeviceCredentials() + { + return LoadDeviceCredentials(null); + } + + /// + /// Loads the device's credentials from the file system + /// + /// URL for the current token issuer + /// Device Credentials (if set) or null + /// + /// The issuerUri can be retrieved from the IServiceConfiguration interface's CurrentIssuer property. + /// + public static ClientCredentials LoadDeviceCredentials(Uri issuerUri) + { + //If the credentials should not be persisted to a file, then they won't be present on the disk. + if (!PersistToFile) + { + return null; + } + + EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri); + + LiveDevice device = ReadExistingDevice(environment); + if (null == device || null == device.User) + { + return null; + } + + return device.User.ToClientCredentials(); + } + + /// + /// Discovers the Microsoft account environment based on the Token Issuer + /// + public static string DiscoverEnvironment(Uri issuerUri) + { + return DiscoverEnvironmentInternal(issuerUri).Environment; + } + #endregion + + #region Private Methods + private static EnvironmentConfiguration DiscoverEnvironmentInternal(Uri issuerUri) + { + if (null == issuerUri) + { + return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, "login.live.com", null); + } + + Dictionary searchList = new Dictionary(); + searchList.Add(EnvironmentType.LiveDeviceID, "login.live"); + searchList.Add(EnvironmentType.OrgDeviceID, "login.microsoftonline"); + + foreach (KeyValuePair searchPair in searchList) + { + if (issuerUri.Host.Length > searchPair.Value.Length && + issuerUri.Host.StartsWith(searchPair.Value, StringComparison.OrdinalIgnoreCase)) + { + string environment = issuerUri.Host.Substring(searchPair.Value.Length); + + //Parse out the environment + if ('-' == environment[0]) + { + int separatorIndex = environment.IndexOf('.', 1); + if (-1 != separatorIndex) + { + environment = environment.Substring(1, separatorIndex - 1); + } + else + { + environment = null; + } + } + else + { + environment = null; + } + + return new EnvironmentConfiguration(searchPair.Key, issuerUri.Host, environment); + } + } + + //In all other cases the environment is either not applicable or it is a production system + return new EnvironmentConfiguration(EnvironmentType.LiveDeviceID, issuerUri.Host, null); + } + + private static void Serialize(Stream stream, T value) + { + XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); + + XmlSerializerNamespaces xmlNamespaces = new XmlSerializerNamespaces(); + xmlNamespaces.Add(string.Empty, string.Empty); + + serializer.Serialize(stream, value, xmlNamespaces); + } + + private static T Deserialize(string operationName, Stream stream) + { + //Read the XML into memory so that the data can be used in an exception if necessary + using (StreamReader reader = new StreamReader(stream)) + { + return Deserialize(operationName, reader.ReadToEnd()); + } + } + + private static T Deserialize(string operationName, string xml) + { + //Attempt to deserialize the data. If deserialization fails, include the XML in the exception that is thrown for further + //investigation + using (StringReader reader = new StringReader(xml)) + { + try + { + XmlSerializer serializer = new XmlSerializer(typeof(T), string.Empty); + return (T)serializer.Deserialize(reader); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Unable to Deserialize XML (Operation = {0}):{1}{2}", operationName, Environment.NewLine, xml), ex); + } + } + } + + private static FileInfo GetDeviceFile(EnvironmentConfiguration environment) + { + return new FileInfo(string.Format(CultureInfo.InvariantCulture, LiveIdConstants.FileNameFormat, + environment.Type, + string.IsNullOrEmpty(environment.Environment) ? null : "-" + environment.Environment.ToUpperInvariant())); + } + + private static ClientCredentials RegisterDevice(Guid applicationId, Uri issuerUri, LiveDevice device) + { + EnvironmentConfiguration environment = DiscoverEnvironmentInternal(issuerUri); + + DeviceRegistrationRequest request = new DeviceRegistrationRequest(applicationId, device); + + string url = string.Format(CultureInfo.InvariantCulture, LiveIdConstants.RegistrationEndpointUriFormat, + environment.HostName); + + DeviceRegistrationResponse response = ExecuteRegistrationRequest(url, request); + if (!response.IsSuccess) + { + bool throwException = true; + if (DeviceRegistrationErrorCode.DeviceAlreadyExists == response.Error.RegistrationErrorCode) + { + if (!PersistToFile) + { + //If the file is not persisted, the registration will always occur (since the credentials are not + //persisted to the disk. However, the credentials may already exist. To avoid an exception being continually + //processed by the calling user, DeviceAlreadyExists will be ignored if the credentials are not persisted to the disk. + return device.User.ToClientCredentials(); + } + else if (PersistIfDeviceAlreadyExists) + { + // This flag indicates that the + throwException = false; + } + } + + if (throwException) + { + throw new DeviceRegistrationFailedException(response.Error.RegistrationErrorCode, response.ErrorSubCode); + } + } + + if (PersistToFile || PersistIfDeviceAlreadyExists) + { + WriteDevice(environment, device); + } + + return device.User.ToClientCredentials(); + } + + private static LiveDevice GenerateDevice(string deviceName, string devicePassword) + { + // If the deviceName hasn't been specified, it should be generated using random characters. + DeviceUserName userNameCredentials; + if (string.IsNullOrEmpty(deviceName)) + { + userNameCredentials = GenerateDeviceUserName(); + } + else + { + userNameCredentials = new DeviceUserName() { DeviceName = deviceName, DecryptedPassword = devicePassword }; + } + + return new LiveDevice() { User = userNameCredentials, Version = 1 }; + } + + private static LiveDevice ReadExistingDevice(EnvironmentConfiguration environment) + { + //Retrieve the file info + FileInfo file = GetDeviceFile(environment); + if (!file.Exists) + { + return null; + } + + using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return Deserialize("Loading Device Credentials from Disk", stream); + } + } + + private static void WriteDevice(EnvironmentConfiguration environment, LiveDevice device) + { + FileInfo file = GetDeviceFile(environment); + if (!file.Directory.Exists) + { + file.Directory.Create(); + } + + using (FileStream stream = file.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None)) + { + Serialize(stream, device); + } + } + + private static DeviceRegistrationResponse ExecuteRegistrationRequest(string url, DeviceRegistrationRequest registrationRequest) + { + //Create the request that will submit the request to the server + WebRequest request = WebRequest.Create(url); + request.ContentType = "application/soap+xml; charset=UTF-8"; + request.Method = "POST"; + request.Timeout = 180000; + + //Write the envelope to the RequestStream + using (Stream stream = request.GetRequestStream()) + { + Serialize(stream, registrationRequest); + } + + // Read the response into an XmlDocument and return that doc + try + { + using (WebResponse response = request.GetResponse()) + { + using (Stream stream = response.GetResponseStream()) + { + return Deserialize("Deserializing Registration Response", stream); + } + } + } + catch (WebException ex) + { + System.Diagnostics.Trace.TraceError("Microsoft account Device Registration Failed (HTTP Code: {0}): {1}", + ex.Status, ex.Message); + + if (null != ex.Response) + { + using (Stream stream = ex.Response.GetResponseStream()) + { + return Deserialize("Deserializing Failed Registration Response", stream); + } + } + + throw; + } + } + + private static DeviceUserName GenerateDeviceUserName() + { + DeviceUserName userName = new DeviceUserName(); + userName.DeviceName = GenerateRandomString(LiveIdConstants.ValidDeviceNameCharacters, MaxDeviceNameLength); + userName.DecryptedPassword = GenerateRandomString(LiveIdConstants.ValidDevicePasswordCharacters, MaxDevicePasswordLength); + + return userName; + } + + private static string GenerateRandomString(string characterSet, int count) + { + //Create an array of the characters that will hold the final list of random characters + char[] value = new char[count]; + + //Convert the character set to an array that can be randomly accessed + char[] set = characterSet.ToCharArray(); + + lock (RandomInstance) + { + //Populate the array with random characters from the character set + for (int i = 0; i < count; i++) + { + value[i] = set[RandomInstance.Next(0, set.Length)]; + } + } + + return new string(value); + } + #endregion + + #region Private Classes + private enum EnvironmentType + { + LiveDeviceID, + OrgDeviceID + } + + private sealed class EnvironmentConfiguration + { + public EnvironmentConfiguration(EnvironmentType type, string hostName, string environment) + { + if (string.IsNullOrWhiteSpace(hostName)) + { + throw new ArgumentNullException("hostName"); + } + + this.Type = type; + this.HostName = hostName; + this.Environment = environment; + } + + #region Properties + public EnvironmentType Type { get; private set; } + + public string HostName { get; private set; } + + public string Environment { get; private set; } + #endregion + } + + private static class LiveIdConstants + { + public const string RegistrationEndpointUriFormat = @"https://{0}/ppsecure/DeviceAddCredential.srf"; + + public static readonly string FileNameFormat = Path.Combine( + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "LiveDeviceID"), + "{0}{1}.xml"); + + public const string ValidDeviceNameCharacters = "0123456789abcdefghijklmnopqrstuvqxyz"; + + //Consists of the list of characters specified in the documentation + public const string ValidDevicePasswordCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^*()-_=+;,./?`~"; + } + #endregion + } + + #region Public Classes & Enums + /// + /// Indicates an error during registration + /// + public enum DeviceRegistrationErrorCode + { + /// + /// Unspecified or Unknown Error occurred + /// + Unknown = 0, + + /// + /// Interface Disabled + /// + InterfaceDisabled = 1, + + /// + /// Invalid Request Format + /// + InvalidRequestFormat = 3, + + /// + /// Unknown Client Version + /// + UnknownClientVersion = 4, + + /// + /// Blank Password + /// + BlankPassword = 6, + + /// + /// Missing Device User Name or Password + /// + MissingDeviceUserNameOrPassword = 7, + + /// + /// Invalid Parameter Syntax + /// + InvalidParameterSyntax = 8, + + /// + /// Invalid Characters are used in the device credentials. + /// + InvalidCharactersInCredentials = 9, + + /// + /// Internal Error + /// + InternalError = 11, + + /// + /// Device Already Exists + /// + DeviceAlreadyExists = 13 + } + + /// + /// Indicates that Device Registration failed + /// + [Serializable] + public sealed class DeviceRegistrationFailedException : Exception + { + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + public DeviceRegistrationFailedException() + : base() + { + } + + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + /// Message to pass + public DeviceRegistrationFailedException(string message) + : base(message) + { + } + + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + /// Message to pass + /// Exception to include + public DeviceRegistrationFailedException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + /// Error code that occurred + /// Subcode that occurred + public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode) + : this(code, subCode, null) + { + } + + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + /// Error code that occurred + /// Subcode that occurred + /// Inner exception + public DeviceRegistrationFailedException(DeviceRegistrationErrorCode code, string subCode, Exception innerException) + : base(string.Concat(code.ToString(), ": ", subCode), innerException) + { + this.RegistrationErrorCode = code; + } + + /// + /// Construct an instance of the DeviceRegistrationFailedException class + /// + /// + /// + private DeviceRegistrationFailedException(SerializationInfo si, StreamingContext sc) + : base(si, sc) + { + } + + #region Properties + /// + /// Error code that occurred during registration + /// + public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; } + #endregion + + #region Methods + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + #endregion + } + + #region Serialization Classes + #region DeviceRegistrationRequest Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("DeviceAddRequest")] + public sealed class DeviceRegistrationRequest + { + #region Constructors + public DeviceRegistrationRequest() + { + } + + public DeviceRegistrationRequest(Guid applicationId, LiveDevice device) + : this() + { + if (null == device) + { + throw new ArgumentNullException("device"); + } + + this.ClientInfo = new DeviceRegistrationClientInfo() { ApplicationId = applicationId, Version = "1.0" }; + this.Authentication = new DeviceRegistrationAuthentication() + { + MemberName = device.User.DeviceId, + Password = device.User.DecryptedPassword + }; + } + #endregion + + #region Properties + [XmlElement("ClientInfo")] + public DeviceRegistrationClientInfo ClientInfo { get; set; } + + [XmlElement("Authentication")] + public DeviceRegistrationAuthentication Authentication { get; set; } + #endregion + } + #endregion + + #region DeviceRegistrationClientInfo Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("ClientInfo")] + public sealed class DeviceRegistrationClientInfo + { + #region Properties + [XmlAttribute("name")] + public Guid ApplicationId { get; set; } + + [XmlAttribute("version")] + public string Version { get; set; } + #endregion + } + #endregion + + #region DeviceRegistrationAuthentication Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("Authentication")] + public sealed class DeviceRegistrationAuthentication + { + #region Properties + [XmlElement("Membername")] + public string MemberName { get; set; } + + [XmlElement("Password")] + public string Password { get; set; } + #endregion + } + #endregion + + #region DeviceRegistrationResponse Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("DeviceAddResponse")] + public sealed class DeviceRegistrationResponse + { + #region Properties + [XmlElement("success")] + public bool IsSuccess { get; set; } + + [XmlElement("puid")] + public string Puid { get; set; } + + [XmlElement("Error")] + public DeviceRegistrationResponseError Error { get; set; } + + [XmlElement("ErrorSubcode")] + public string ErrorSubCode { get; set; } + #endregion + } + #endregion + + #region DeviceRegistrationResponse Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("Error")] + public sealed class DeviceRegistrationResponseError + { + private string _code; + + #region Properties + [XmlAttribute("Code")] + public string Code + { + get + { + return this._code; + } + + set + { + this._code = value; + + //Parse the error code + if (!string.IsNullOrEmpty(value)) + { + //Parse the error code + if (value.StartsWith("dc", StringComparison.Ordinal)) + { + int code; + if (int.TryParse(value.Substring(2), NumberStyles.Integer, + CultureInfo.InvariantCulture, out code) && + Enum.IsDefined(typeof(DeviceRegistrationErrorCode), code)) + { + this.RegistrationErrorCode = (DeviceRegistrationErrorCode)Enum.ToObject( + typeof(DeviceRegistrationErrorCode), code); + } + } + } + } + } + + [XmlIgnore] + public DeviceRegistrationErrorCode RegistrationErrorCode { get; private set; } + #endregion + } + #endregion + + #region LiveDevice Class + [EditorBrowsable(EditorBrowsableState.Never)] + [XmlRoot("Data")] + public sealed class LiveDevice + { + #region Properties + [XmlAttribute("version")] + public int Version { get; set; } + + [XmlElement("User")] + public DeviceUserName User { get; set; } + + [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", MessageId = "System.Xml.XmlNode", Justification = "This is required for proper XML Serialization")] + [XmlElement("Token")] + public XmlNode Token { get; set; } + + [XmlElement("Expiry")] + public string Expiry { get; set; } + + [XmlElement("ClockSkew")] + public string ClockSkew { get; set; } + #endregion + } + #endregion + + #region DeviceUserName Class + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class DeviceUserName + { + private string _encryptedPassword; + private string _decryptedPassword; + private bool _encryptedValueIsUpdated; + + #region Constants + private const string UserNamePrefix = "11"; + #endregion + + #region Constructors + public DeviceUserName() + { + this.UserNameType = "Logical"; + } + #endregion + + #region Properties + [XmlAttribute("username")] + public string DeviceName { get; set; } + + [XmlAttribute("type")] + public string UserNameType { get; set; } + + [XmlElement("Pwd")] + public string EncryptedPassword + { + get + { + this.ThrowIfNoEncryption(); + + if (!this._encryptedValueIsUpdated) + { + this._encryptedPassword = this.Encrypt(this._decryptedPassword); + this._encryptedValueIsUpdated = true; + } + + return this._encryptedPassword; + } + + set + { + this.ThrowIfNoEncryption(); + this.UpdateCredentials(value, null); + } + } + + public string DeviceId + { + get + { + return UserNamePrefix + DeviceName; + } + } + + [XmlIgnore] + public string DecryptedPassword + { + get + { + return this._decryptedPassword; + } + + set + { + this.UpdateCredentials(null, value); + } + } + + private bool IsEncryptionEnabled + { + get + { + //If the object is not going to be persisted to a file, then the value does not need to be encrypted. This is extra + //overhead and will not function in partial trust. + return DeviceIdManager.PersistToFile; + } + } + #endregion + + #region Methods + public ClientCredentials ToClientCredentials() + { + ClientCredentials credentials = new ClientCredentials(); + credentials.UserName.UserName = this.DeviceId; + credentials.UserName.Password = this.DecryptedPassword; + + return credentials; + } + + private void ThrowIfNoEncryption() + { + if (!this.IsEncryptionEnabled) + { + throw new NotSupportedException("Not supported when DeviceIdManager.UseEncryptionApis is false."); + } + } + + private void UpdateCredentials(string encryptedValue, string decryptedValue) + { + bool isValueUpdated = false; + if (string.IsNullOrEmpty(encryptedValue) && string.IsNullOrEmpty(decryptedValue)) + { + isValueUpdated = true; + } + else if (string.IsNullOrEmpty(encryptedValue)) + { + if (this.IsEncryptionEnabled) + { + encryptedValue = this.Encrypt(decryptedValue); + isValueUpdated = true; + } + else + { + encryptedValue = null; + isValueUpdated = false; + } + } + else + { + this.ThrowIfNoEncryption(); + + decryptedValue = this.Decrypt(encryptedValue); + isValueUpdated = true; + } + + this._encryptedPassword = encryptedValue; + this._decryptedPassword = decryptedValue; + this._encryptedValueIsUpdated = isValueUpdated; + } + + private string Encrypt(string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + byte[] encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), null, DataProtectionScope.CurrentUser); + return Convert.ToBase64String(encryptedBytes); + } + + private string Decrypt(string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + byte[] decryptedBytes = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser); + if (null == decryptedBytes || 0 == decryptedBytes.Length) + { + return null; + } + + return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length); + } + #endregion + } + #endregion + #endregion + #endregion +} +// diff --git a/src/Microsoft.Crm.Services.Utility/paket.references b/src/Microsoft.Crm.Services.Utility/paket.references new file mode 100644 index 0000000..08db74d --- /dev/null +++ b/src/Microsoft.Crm.Services.Utility/paket.references @@ -0,0 +1 @@ +Microsoft.CrmSdk.CoreAssemblies \ No newline at end of file