├── .gitignore ├── ADPickerTester ├── ADPickerTester.csproj ├── App.ico ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx └── app.config ├── Active Directory Object Picker.sln ├── LICENSE ├── README.md ├── Screenshots ├── ADPickerTester.gif ├── img01.png ├── img02.png └── img03.png ├── Tulpep.ActiveDirectoryObjectPicker ├── ADsPathsProviders.cs ├── ComInterop.cs ├── DirectoryObject.cs ├── DirectoryObjectPickerDialog.cs ├── EnumExtensions.cs ├── ExtensionAttributeFor.NET_2.0.cs ├── IntPtrExtensions.cs ├── Locations.cs ├── NameTranslator.cs ├── ObjectTypes.cs ├── PInvoke.cs ├── StructsFlags.cs ├── Tulpep.ActiveDirectoryObjectPicker.csproj ├── Tulpep.ActiveDirectoryObjectPicker.snk └── UnmanagedArrayOfStrings.cs └── appveyor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Visual Studio cache 5 | .vs/ 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.sln.docstates 11 | 12 | # Build results 13 | 14 | [Dd]ebug/ 15 | [Rr]elease/ 16 | x64/ 17 | build/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 22 | !packages/*/build/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | *_i.c 29 | *_p.c 30 | *.ilk 31 | *.meta 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.tmp_proj 44 | *.log 45 | *.vspscc 46 | *.vssscc 47 | .builds 48 | *.pidb 49 | *.log 50 | *.scc 51 | 52 | # Visual C++ cache files 53 | ipch/ 54 | *.aps 55 | *.ncb 56 | *.opensdf 57 | *.sdf 58 | *.cachefile 59 | 60 | # Visual Studio profiler 61 | *.psess 62 | *.vsp 63 | *.vspx 64 | 65 | # Guidance Automation Toolkit 66 | *.gpState 67 | 68 | # ReSharper is a .NET coding add-in 69 | _ReSharper*/ 70 | *.[Rr]e[Ss]harper 71 | 72 | # TeamCity is a build add-in 73 | _TeamCity* 74 | 75 | # DotCover is a Code Coverage Tool 76 | *.dotCover 77 | 78 | # NCrunch 79 | *.ncrunch* 80 | .*crunch*.local.xml 81 | 82 | # Installshield output folder 83 | [Ee]xpress/ 84 | 85 | # DocProject is a documentation generator add-in 86 | DocProject/buildhelp/ 87 | DocProject/Help/*.HxT 88 | DocProject/Help/*.HxC 89 | DocProject/Help/*.hhc 90 | DocProject/Help/*.hhk 91 | DocProject/Help/*.hhp 92 | DocProject/Help/Html2 93 | DocProject/Help/html 94 | 95 | # Click-Once directory 96 | publish/ 97 | 98 | # Publish Web Output 99 | *.Publish.xml 100 | 101 | # NuGet Packages Directory 102 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 103 | packages/ 104 | 105 | # Visual Studio 2017 project directory 106 | .vs/ 107 | 108 | # Windows Azure Build Output 109 | csx 110 | *.build.csdef 111 | 112 | # Windows Store app package directory 113 | AppPackages/ 114 | 115 | # Others 116 | sql/ 117 | *.Cache 118 | ClientBin/ 119 | [Ss]tyle[Cc]op.* 120 | ~$* 121 | *~ 122 | *.dbmdl 123 | *.[Pp]ublish.xml 124 | *.publishsettings 125 | 126 | # RIA/Silverlight projects 127 | Generated_Code/ 128 | 129 | # Backup & report files from converting an old project file to a newer 130 | # Visual Studio version. Backup files are not needed, because we have git ;-) 131 | _UpgradeReport_Files/ 132 | Backup*/ 133 | UpgradeLog*.XML 134 | UpgradeLog*.htm 135 | 136 | # SQL Server files 137 | *.mdf 138 | *.ldf 139 | 140 | 141 | #LightSwitch generated files 142 | GeneratedArtifacts/ 143 | _Pvt_Extensions/ 144 | ModelManifest.xml 145 | 146 | # ========================= 147 | # Windows detritus 148 | # ========================= 149 | 150 | # Windows image file caches 151 | Thumbs.db 152 | ehthumbs.db 153 | 154 | # Folder config file 155 | Desktop.ini 156 | 157 | # Recycle Bin used on file shares 158 | $RECYCLE.BIN/ 159 | 160 | # Mac desktop service store files 161 | .DS_Store 162 | 163 | #Nuget builder process 164 | nuget.exe 165 | *.nupkg 166 | -------------------------------------------------------------------------------- /ADPickerTester/ADPickerTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net472 5 | true 6 | true 7 | ADPickerTester 8 | 9 | Directory Object Picker 10 | © 2004 Armand du Plessis <armand@dotnet.org.za> 11 | 2.0.0.0 12 | 2.0.0 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ADPickerTester/App.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/ADPickerTester/App.ico -------------------------------------------------------------------------------- /ADPickerTester/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ADPickerTester 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | 24 | #region Windows Form Designer generated code 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.btnInvoke = new System.Windows.Forms.Button(); 32 | this.chklistDefaultTypes = new System.Windows.Forms.CheckedListBox(); 33 | this.label1 = new System.Windows.Forms.Label(); 34 | this.chkMultiSelect = new System.Windows.Forms.CheckBox(); 35 | this.label2 = new System.Windows.Forms.Label(); 36 | this.chklistAllowedTypes = new System.Windows.Forms.CheckedListBox(); 37 | this.label3 = new System.Windows.Forms.Label(); 38 | this.chklistAllowedLocations = new System.Windows.Forms.CheckedListBox(); 39 | this.label4 = new System.Windows.Forms.Label(); 40 | this.chklistDefaultLocations = new System.Windows.Forms.CheckedListBox(); 41 | this.chkShowAdvanced = new System.Windows.Forms.CheckBox(); 42 | this.txtTargetComputer = new System.Windows.Forms.TextBox(); 43 | this.label5 = new System.Windows.Forms.Label(); 44 | this.chklistAttributes = new System.Windows.Forms.CheckedListBox(); 45 | this.label6 = new System.Windows.Forms.Label(); 46 | this.chkSkipDcCheck = new System.Windows.Forms.CheckBox(); 47 | this.comboPathProvider = new System.Windows.Forms.ComboBox(); 48 | this.label7 = new System.Windows.Forms.Label(); 49 | this.txtFeedback = new System.Windows.Forms.TextBox(); 50 | this.label8 = new System.Windows.Forms.Label(); 51 | this.txUserName = new System.Windows.Forms.TextBox(); 52 | this.label9 = new System.Windows.Forms.Label(); 53 | this.txPassword = new System.Windows.Forms.TextBox(); 54 | this.SuspendLayout(); 55 | // 56 | // btnInvoke 57 | // 58 | this.btnInvoke.Location = new System.Drawing.Point(530, 172); 59 | this.btnInvoke.Name = "btnInvoke"; 60 | this.btnInvoke.Size = new System.Drawing.Size(121, 39); 61 | this.btnInvoke.TabIndex = 0; 62 | this.btnInvoke.Text = "Directory Object Picker"; 63 | this.btnInvoke.Click += new System.EventHandler(this.btnInvoke_Click); 64 | // 65 | // chklistDefaultTypes 66 | // 67 | this.chklistDefaultTypes.FormattingEnabled = true; 68 | this.chklistDefaultTypes.Location = new System.Drawing.Point(279, 25); 69 | this.chklistDefaultTypes.Name = "chklistDefaultTypes"; 70 | this.chklistDefaultTypes.Size = new System.Drawing.Size(120, 124); 71 | this.chklistDefaultTypes.TabIndex = 6; 72 | // 73 | // label1 74 | // 75 | this.label1.AutoSize = true; 76 | this.label1.Location = new System.Drawing.Point(279, 9); 77 | this.label1.Name = "label1"; 78 | this.label1.Size = new System.Drawing.Size(73, 13); 79 | this.label1.TabIndex = 5; 80 | this.label1.Text = "Default Types"; 81 | // 82 | // chkMultiSelect 83 | // 84 | this.chkMultiSelect.AutoSize = true; 85 | this.chkMultiSelect.Location = new System.Drawing.Point(279, 199); 86 | this.chkMultiSelect.Name = "chkMultiSelect"; 87 | this.chkMultiSelect.Size = new System.Drawing.Size(81, 17); 88 | this.chkMultiSelect.TabIndex = 19; 89 | this.chkMultiSelect.Text = "Multi-Select"; 90 | this.chkMultiSelect.UseVisualStyleBackColor = true; 91 | // 92 | // label2 93 | // 94 | this.label2.AutoSize = true; 95 | this.label2.Location = new System.Drawing.Point(12, 9); 96 | this.label2.Name = "label2"; 97 | this.label2.Size = new System.Drawing.Size(76, 13); 98 | this.label2.TabIndex = 1; 99 | this.label2.Text = "Allowed Types"; 100 | // 101 | // chklistAllowedTypes 102 | // 103 | this.chklistAllowedTypes.FormattingEnabled = true; 104 | this.chklistAllowedTypes.Location = new System.Drawing.Point(12, 25); 105 | this.chklistAllowedTypes.Name = "chklistAllowedTypes"; 106 | this.chklistAllowedTypes.Size = new System.Drawing.Size(120, 124); 107 | this.chklistAllowedTypes.TabIndex = 2; 108 | // 109 | // label3 110 | // 111 | this.label3.AutoSize = true; 112 | this.label3.Location = new System.Drawing.Point(138, 9); 113 | this.label3.Name = "label3"; 114 | this.label3.Size = new System.Drawing.Size(93, 13); 115 | this.label3.TabIndex = 3; 116 | this.label3.Text = "Allowed Locations"; 117 | // 118 | // chklistAllowedLocations 119 | // 120 | this.chklistAllowedLocations.FormattingEnabled = true; 121 | this.chklistAllowedLocations.Location = new System.Drawing.Point(138, 25); 122 | this.chklistAllowedLocations.Name = "chklistAllowedLocations"; 123 | this.chklistAllowedLocations.Size = new System.Drawing.Size(120, 124); 124 | this.chklistAllowedLocations.TabIndex = 4; 125 | // 126 | // label4 127 | // 128 | this.label4.AutoSize = true; 129 | this.label4.Location = new System.Drawing.Point(405, 9); 130 | this.label4.Name = "label4"; 131 | this.label4.Size = new System.Drawing.Size(90, 13); 132 | this.label4.TabIndex = 7; 133 | this.label4.Text = "Default Locations"; 134 | // 135 | // chklistDefaultLocations 136 | // 137 | this.chklistDefaultLocations.FormattingEnabled = true; 138 | this.chklistDefaultLocations.Location = new System.Drawing.Point(405, 25); 139 | this.chklistDefaultLocations.Name = "chklistDefaultLocations"; 140 | this.chklistDefaultLocations.Size = new System.Drawing.Size(120, 124); 141 | this.chklistDefaultLocations.TabIndex = 8; 142 | // 143 | // chkShowAdvanced 144 | // 145 | this.chkShowAdvanced.AutoSize = true; 146 | this.chkShowAdvanced.Location = new System.Drawing.Point(279, 181); 147 | this.chkShowAdvanced.Name = "chkShowAdvanced"; 148 | this.chkShowAdvanced.Size = new System.Drawing.Size(105, 17); 149 | this.chkShowAdvanced.TabIndex = 18; 150 | this.chkShowAdvanced.Text = "Show Advanced"; 151 | this.chkShowAdvanced.UseVisualStyleBackColor = true; 152 | // 153 | // txtTargetComputer 154 | // 155 | this.txtTargetComputer.Location = new System.Drawing.Point(104, 163); 156 | this.txtTargetComputer.Name = "txtTargetComputer"; 157 | this.txtTargetComputer.Size = new System.Drawing.Size(154, 20); 158 | this.txtTargetComputer.TabIndex = 12; 159 | // 160 | // label5 161 | // 162 | this.label5.AutoSize = true; 163 | this.label5.Location = new System.Drawing.Point(12, 167); 164 | this.label5.Name = "label5"; 165 | this.label5.Size = new System.Drawing.Size(86, 13); 166 | this.label5.TabIndex = 11; 167 | this.label5.Text = "Target Computer"; 168 | // 169 | // chklistAttributes 170 | // 171 | this.chklistAttributes.FormattingEnabled = true; 172 | this.chklistAttributes.Location = new System.Drawing.Point(531, 25); 173 | this.chklistAttributes.Name = "chklistAttributes"; 174 | this.chklistAttributes.Size = new System.Drawing.Size(120, 124); 175 | this.chklistAttributes.TabIndex = 10; 176 | // 177 | // label6 178 | // 179 | this.label6.AutoSize = true; 180 | this.label6.Location = new System.Drawing.Point(531, 9); 181 | this.label6.Name = "label6"; 182 | this.label6.Size = new System.Drawing.Size(93, 13); 183 | this.label6.TabIndex = 9; 184 | this.label6.Text = "Attributes to Fetch"; 185 | // 186 | // chkSkipDcCheck 187 | // 188 | this.chkSkipDcCheck.AutoSize = true; 189 | this.chkSkipDcCheck.Checked = true; 190 | this.chkSkipDcCheck.CheckState = System.Windows.Forms.CheckState.Checked; 191 | this.chkSkipDcCheck.Location = new System.Drawing.Point(279, 163); 192 | this.chkSkipDcCheck.Name = "chkSkipDcCheck"; 193 | this.chkSkipDcCheck.Size = new System.Drawing.Size(98, 17); 194 | this.chkSkipDcCheck.TabIndex = 17; 195 | this.chkSkipDcCheck.Text = "Skip DC check"; 196 | this.chkSkipDcCheck.UseVisualStyleBackColor = true; 197 | // 198 | // comboPathProvider 199 | // 200 | this.comboPathProvider.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 201 | this.comboPathProvider.FormattingEnabled = true; 202 | this.comboPathProvider.Location = new System.Drawing.Point(403, 182); 203 | this.comboPathProvider.Name = "comboPathProvider"; 204 | this.comboPathProvider.Size = new System.Drawing.Size(121, 21); 205 | this.comboPathProvider.TabIndex = 21; 206 | // 207 | // label7 208 | // 209 | this.label7.AutoSize = true; 210 | this.label7.Location = new System.Drawing.Point(405, 163); 211 | this.label7.Name = "label7"; 212 | this.label7.Size = new System.Drawing.Size(71, 13); 213 | this.label7.TabIndex = 20; 214 | this.label7.Text = "Path Provider"; 215 | // 216 | // txtFeedback 217 | // 218 | this.txtFeedback.Dock = System.Windows.Forms.DockStyle.Bottom; 219 | this.txtFeedback.Location = new System.Drawing.Point(0, 222); 220 | this.txtFeedback.Multiline = true; 221 | this.txtFeedback.Name = "txtFeedback"; 222 | this.txtFeedback.ReadOnly = true; 223 | this.txtFeedback.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 224 | this.txtFeedback.Size = new System.Drawing.Size(666, 237); 225 | this.txtFeedback.TabIndex = 22; 226 | // 227 | // label8 228 | // 229 | this.label8.AutoSize = true; 230 | this.label8.Location = new System.Drawing.Point(12, 193); 231 | this.label8.Name = "label8"; 232 | this.label8.Size = new System.Drawing.Size(29, 13); 233 | this.label8.TabIndex = 13; 234 | this.label8.Text = "User"; 235 | // 236 | // txUserName 237 | // 238 | this.txUserName.Location = new System.Drawing.Point(47, 190); 239 | this.txUserName.Name = "txUserName"; 240 | this.txUserName.Size = new System.Drawing.Size(85, 20); 241 | this.txUserName.TabIndex = 14; 242 | // 243 | // label9 244 | // 245 | this.label9.AutoSize = true; 246 | this.label9.Location = new System.Drawing.Point(138, 193); 247 | this.label9.Name = "label9"; 248 | this.label9.Size = new System.Drawing.Size(28, 13); 249 | this.label9.TabIndex = 15; 250 | this.label9.Text = "Pwd"; 251 | // 252 | // txPassword 253 | // 254 | this.txPassword.Location = new System.Drawing.Point(173, 190); 255 | this.txPassword.Name = "txPassword"; 256 | this.txPassword.Size = new System.Drawing.Size(85, 20); 257 | this.txPassword.TabIndex = 16; 258 | // 259 | // MainForm 260 | // 261 | this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); 262 | this.ClientSize = new System.Drawing.Size(666, 459); 263 | this.Controls.Add(this.txPassword); 264 | this.Controls.Add(this.label9); 265 | this.Controls.Add(this.txUserName); 266 | this.Controls.Add(this.label8); 267 | this.Controls.Add(this.txtFeedback); 268 | this.Controls.Add(this.comboPathProvider); 269 | this.Controls.Add(this.label5); 270 | this.Controls.Add(this.txtTargetComputer); 271 | this.Controls.Add(this.chkShowAdvanced); 272 | this.Controls.Add(this.label6); 273 | this.Controls.Add(this.label7); 274 | this.Controls.Add(this.label4); 275 | this.Controls.Add(this.chklistAttributes); 276 | this.Controls.Add(this.chklistDefaultLocations); 277 | this.Controls.Add(this.label3); 278 | this.Controls.Add(this.chklistAllowedLocations); 279 | this.Controls.Add(this.label2); 280 | this.Controls.Add(this.chklistAllowedTypes); 281 | this.Controls.Add(this.chkSkipDcCheck); 282 | this.Controls.Add(this.chkMultiSelect); 283 | this.Controls.Add(this.label1); 284 | this.Controls.Add(this.chklistDefaultTypes); 285 | this.Controls.Add(this.btnInvoke); 286 | this.Name = "MainForm"; 287 | this.Text = "Directory Object Picker Tester"; 288 | this.Load += new System.EventHandler(this.Form1_Load); 289 | this.ResumeLayout(false); 290 | this.PerformLayout(); 291 | 292 | } 293 | #endregion 294 | 295 | private System.Windows.Forms.Button btnInvoke; 296 | private System.Windows.Forms.CheckedListBox chklistDefaultTypes; 297 | private System.Windows.Forms.Label label1; 298 | private System.Windows.Forms.CheckBox chkMultiSelect; 299 | private System.Windows.Forms.Label label2; 300 | private System.Windows.Forms.CheckedListBox chklistAllowedTypes; 301 | private System.Windows.Forms.Label label3; 302 | private System.Windows.Forms.CheckedListBox chklistAllowedLocations; 303 | private System.Windows.Forms.Label label4; 304 | private System.Windows.Forms.CheckedListBox chklistDefaultLocations; 305 | private System.Windows.Forms.CheckBox chkShowAdvanced; 306 | private System.Windows.Forms.TextBox txtTargetComputer; 307 | private System.Windows.Forms.Label label5; 308 | private System.Windows.Forms.CheckedListBox chklistAttributes; 309 | private System.Windows.Forms.Label label6; 310 | private System.Windows.Forms.CheckBox chkSkipDcCheck; 311 | private System.Windows.Forms.ComboBox comboPathProvider; 312 | private System.Windows.Forms.Label label7; 313 | private System.Windows.Forms.TextBox txtFeedback; 314 | private System.Windows.Forms.Label label8; 315 | private System.Windows.Forms.TextBox txUserName; 316 | private System.Windows.Forms.Label label9; 317 | private System.Windows.Forms.TextBox txPassword; 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /ADPickerTester/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Security.Principal; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | 7 | using Tulpep.ActiveDirectoryObjectPicker; 8 | 9 | namespace ADPickerTester 10 | { 11 | /// Summary description for Form1. 12 | public partial class MainForm : Form 13 | { 14 | public MainForm() => 15 | // Required for Windows Form Designer support 16 | InitializeComponent(); 17 | 18 | /// The main entry point for the application. 19 | [STAThread] 20 | private static void Main() => Application.Run(new MainForm()); 21 | 22 | private void btnInvoke_Click(object sender, EventArgs e) 23 | { 24 | try 25 | { 26 | var allowedTypes = ObjectTypes.None; 27 | foreach (ObjectTypes value in chklistAllowedTypes.CheckedItems) 28 | { 29 | allowedTypes |= value; 30 | } 31 | var defaultTypes = ObjectTypes.None; 32 | foreach (ObjectTypes value in chklistDefaultTypes.CheckedItems) 33 | { 34 | defaultTypes |= value; 35 | } 36 | var allowedLocations = Locations.None; 37 | foreach (Locations value in chklistAllowedLocations.CheckedItems) 38 | { 39 | allowedLocations |= value; 40 | } 41 | var defaultLocations = Locations.None; 42 | foreach (Locations value in chklistDefaultLocations.CheckedItems) 43 | { 44 | defaultLocations |= value; 45 | } 46 | // Show dialog 47 | var picker = new DirectoryObjectPickerDialog 48 | { 49 | AllowedObjectTypes = allowedTypes, 50 | DefaultObjectTypes = defaultTypes, 51 | AllowedLocations = allowedLocations, 52 | DefaultLocations = defaultLocations, 53 | MultiSelect = chkMultiSelect.Checked, 54 | TargetComputer = txtTargetComputer.Text, 55 | SkipDomainControllerCheck = chkSkipDcCheck.Checked, 56 | ShowAdvancedView = chkShowAdvanced.Checked 57 | }; 58 | 59 | if (comboPathProvider.SelectedItem is ADsPathsProviders) 60 | picker.Providers = (ADsPathsProviders)comboPathProvider.SelectedItem; 61 | 62 | if (txUserName.TextLength > 0) 63 | picker.SetCredentials(txUserName.Text, txPassword.Text); 64 | 65 | foreach (string attribute in chklistAttributes.CheckedItems) 66 | { 67 | var trimmed = attribute.Trim(); 68 | if (trimmed.Length > 0) 69 | picker.AttributesToFetch.Add(trimmed); 70 | } 71 | var dialogResult = picker.ShowDialog(this); 72 | if (dialogResult == DialogResult.OK) 73 | { 74 | var results = picker.SelectedObjects; 75 | if (results == null) 76 | { 77 | txtFeedback.Text = "Results null."; 78 | return; 79 | } 80 | 81 | var sb = new StringBuilder(); 82 | 83 | for (var i = 0; i <= results.Length - 1; i++) 84 | { 85 | sb.Append(string.Format("Name: \t\t {0}", results[i].Name)); 86 | sb.Append(Environment.NewLine); 87 | sb.Append(string.Format("UPN: \t\t {0}", results[i].Upn)); 88 | sb.Append(Environment.NewLine); 89 | sb.Append(string.Format("Path: \t\t {0}", results[i].Path)); 90 | sb.Append(Environment.NewLine); 91 | sb.Append(string.Format("Schema Class: \t\t {0} ", results[i].SchemaClassName)); 92 | sb.Append(Environment.NewLine); 93 | var downLevelName = ""; 94 | try 95 | { 96 | if (!string.IsNullOrEmpty(results[i].Upn)) 97 | downLevelName = NameTranslator.TranslateUpnToDownLevel(results[i].Upn); 98 | } 99 | catch (Exception ex) 100 | { 101 | downLevelName = string.Format("{0}: {1}", ex.GetType().Name, ex.Message); 102 | } 103 | sb.Append(string.Format("Down-level Name: \t\t {0} ", downLevelName)); 104 | sb.Append(Environment.NewLine); 105 | sb.AppendFormat("Fetched {0} attributes", results[i].FetchedAttributes.Length); 106 | sb.Append(Environment.NewLine); 107 | for (var j = 0; j < results[i].FetchedAttributes.Length; j++) 108 | { 109 | var attributeName = picker.AttributesToFetch[j]; 110 | sb.AppendFormat("\t{0}. {1}", j, attributeName); 111 | sb.Append(Environment.NewLine); 112 | 113 | var multivaluedAttribute = results[i].FetchedAttributes[j]; 114 | if (!(multivaluedAttribute is IEnumerable) || multivaluedAttribute is byte[] || multivaluedAttribute is string) 115 | multivaluedAttribute = new[] { multivaluedAttribute }; 116 | 117 | foreach (var attribute in (IEnumerable)multivaluedAttribute) 118 | { 119 | sb.Append("\t"); 120 | if (attribute == null) 121 | { 122 | sb.Append("(not present)"); 123 | } 124 | else if (attribute is byte[]) 125 | { 126 | var bytes = (byte[])attribute; 127 | if (attributeName.Equals("objectSid", StringComparison.OrdinalIgnoreCase)) 128 | { 129 | sb.Append(SIDBytesToString(bytes)); 130 | } 131 | else 132 | { 133 | sb.Append(GuidBytesToString(bytes)); 134 | } 135 | } 136 | else 137 | { 138 | sb.AppendFormat("{0}", attribute); 139 | } 140 | sb.Append(Environment.NewLine); 141 | } 142 | 143 | sb.Append(Environment.NewLine); 144 | } 145 | 146 | sb.Append(Environment.NewLine); 147 | sb.Append(Environment.NewLine); 148 | } 149 | txtFeedback.Text = sb.ToString(); 150 | } 151 | else 152 | { 153 | txtFeedback.Text = "Dialog result: " + dialogResult; 154 | } 155 | } 156 | catch (Exception e1) 157 | { 158 | MessageBox.Show(e1.ToString()); 159 | } 160 | } 161 | 162 | private string BytesToString(byte[] bytes) 163 | { 164 | return "0x" + BitConverter.ToString(bytes).Replace('-', ' '); 165 | } 166 | 167 | private string GuidBytesToString(byte[] bytes) 168 | { 169 | try 170 | { 171 | var guid = new Guid(bytes); 172 | return guid.ToString("D"); 173 | } 174 | // ReSharper disable once EmptyGeneralCatchClause 175 | catch (Exception) 176 | { 177 | } 178 | 179 | return BytesToString(bytes); 180 | } 181 | 182 | private string SIDBytesToString(byte[] bytes) 183 | { 184 | try 185 | { 186 | var sid = new SecurityIdentifier(bytes, 0); 187 | return sid.ToString(); 188 | } 189 | // ReSharper disable once EmptyGeneralCatchClause 190 | catch (Exception) 191 | { 192 | } 193 | 194 | return BytesToString(bytes); 195 | } 196 | 197 | private void Form1_Load(object sender, EventArgs e) 198 | { 199 | chklistAllowedTypes.Items.Clear(); 200 | chklistDefaultTypes.Items.Clear(); 201 | foreach (ObjectTypes objectType in Enum.GetValues(typeof(ObjectTypes))) 202 | { 203 | if (objectType != ObjectTypes.None && objectType != ObjectTypes.All) 204 | { 205 | chklistAllowedTypes.Items.Add(objectType, CheckState.Checked); 206 | if (objectType == ObjectTypes.Users || objectType == ObjectTypes.Groups 207 | || objectType == ObjectTypes.Computers || objectType == ObjectTypes.Contacts) 208 | { 209 | chklistDefaultTypes.Items.Add(objectType, CheckState.Checked); 210 | } 211 | else 212 | { 213 | chklistDefaultTypes.Items.Add(objectType, CheckState.Unchecked); 214 | } 215 | } 216 | } 217 | 218 | chklistAllowedLocations.Items.Clear(); 219 | chklistDefaultLocations.Items.Clear(); 220 | foreach (Locations location in Enum.GetValues(typeof(Locations))) 221 | { 222 | if (location != Locations.None && location != Locations.All) 223 | { 224 | chklistAllowedLocations.Items.Add(location, CheckState.Checked); 225 | if (location == Locations.JoinedDomain) 226 | { 227 | chklistDefaultLocations.Items.Add(location, CheckState.Checked); 228 | } 229 | else 230 | { 231 | chklistDefaultLocations.Items.Add(location, CheckState.Unchecked); 232 | } 233 | } 234 | } 235 | 236 | comboPathProvider.Items.Clear(); 237 | foreach (ADsPathsProviders provider in Enum.GetValues(typeof(ADsPathsProviders))) 238 | { 239 | comboPathProvider.Items.Add(provider); 240 | } 241 | comboPathProvider.SelectedIndex = 0; 242 | 243 | chklistAttributes.Items.Clear(); 244 | //to find more, go to http://msdn.microsoft.com/en-us/library/cc219752.aspx, http://msdn.microsoft.com/en-us/library/cc220155.aspx and http://msdn.microsoft.com/en-us/library/cc220700.aspx 245 | chklistAttributes.Items.AddRange(new object[] 246 | { 247 | "objectSid", 248 | }); 249 | } 250 | } 251 | } -------------------------------------------------------------------------------- /ADPickerTester/MainForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ADPickerTester/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Active Directory Object Picker.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ADPickerTester", "ADPickerTester\ADPickerTester.csproj", "{46550771-182F-4A29-9879-AD837D0A9D10}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tulpep.ActiveDirectoryObjectPicker", "Tulpep.ActiveDirectoryObjectPicker\Tulpep.ActiveDirectoryObjectPicker.csproj", "{AD25BE24-8A42-44E0-9588-3767F572C338}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution files", "Solution files", "{3CFB5347-F5F1-4097-B9F7-0880E1B1CA57}" 11 | ProjectSection(SolutionItems) = preProject 12 | appveyor.yml = appveyor.yml 13 | LICENSE = LICENSE 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Screenshots", "Screenshots", "{81FE7AEB-CEBA-425A-8CFA-B1AE18185813}" 18 | ProjectSection(SolutionItems) = preProject 19 | Screenshots\ADPickerTester.gif = Screenshots\ADPickerTester.gif 20 | Screenshots\img01.png = Screenshots\img01.png 21 | Screenshots\img02.png = Screenshots\img02.png 22 | Screenshots\img03.png = Screenshots\img03.png 23 | EndProjectSection 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {46550771-182F-4A29-9879-AD837D0A9D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {46550771-182F-4A29-9879-AD837D0A9D10}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {46550771-182F-4A29-9879-AD837D0A9D10}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {46550771-182F-4A29-9879-AD837D0A9D10}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {AD25BE24-8A42-44E0-9588-3767F572C338}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {AD25BE24-8A42-44E0-9588-3767F572C338}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {AD25BE24-8A42-44E0-9588-3767F572C338}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {AD25BE24-8A42-44E0-9588-3767F572C338}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(SolutionProperties) = preSolution 41 | HideSolutionNode = FALSE 42 | EndGlobalSection 43 | GlobalSection(NestedProjects) = preSolution 44 | {81FE7AEB-CEBA-425A-8CFA-B1AE18185813} = {3CFB5347-F5F1-4097-B9F7-0880E1B1CA57} 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {EAC3C911-4E73-492D-B434-3F7FDDC11C8F} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (MS-PL) 2 | 3 | The initial project was originally created by Armand du Plessis in 2004 and now is extended and maintained by Tulpep. 4 | 5 | This license governs use of the accompanying software. If you use the software, you 6 | accept this license. If you do not accept the license, do not use the software. 7 | 8 | 1. Definitions 9 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the 10 | same meaning here as under U.S. copyright law. 11 | A "contribution" is the original software, or any additions or changes to the software. 12 | A "contributor" is any person that distributes its contribution under this license. 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 17 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 18 | 19 | 3. Conditions and Limitations 20 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 21 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 22 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 23 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 24 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Active Directory Object Picker [![Build status](https://ci.appveyor.com/api/projects/status/q5ttquqholl8oomi?svg=true)](https://ci.appveyor.com/project/Boardgent/active-directory-object-picker) 2 | 3 | ======================== 4 | ### The standard Active Directory object picker dialog for .NET 5 | 6 | ![Screenshot](Screenshots/ADPickerTester.gif) 7 | 8 | 9 | This project is based on a [Active Directory Common Dialogs .NET (ADUI)](https://adui.codeplex.com/) created in 2004 by Armand du Plessis. It has been updated and make it working with 64 bit Windows Editions. 10 | Now it works in all .Net framework versions, including version 2.0, 3.5, 4.0, 4.5 and .NET Core. 11 | 12 | ### How to use it 13 | You can install the latest version using [NuGet](https://www.nuget.org/packages/Tulpep.ActiveDirectoryObjectPicker/) 14 | ```powershell 15 | Install-Package Tulpep.ActiveDirectoryObjectPicker 16 | ``` 17 | 18 | And use it this way: 19 | ```cs 20 | DirectoryObjectPickerDialog picker = new DirectoryObjectPickerDialog() 21 | { 22 | AllowedObjectTypes = ObjectTypes.Users | ObjectTypes.Groups | ObjectTypes.Computers, 23 | DefaultObjectTypes = ObjectTypes.Computers, 24 | AllowedLocations = Locations.All, 25 | DefaultLocations = Locations.JoinedDomain, 26 | MultiSelect = true, 27 | ShowAdvancedView = true 28 | }; 29 | using (picker) 30 | { 31 | if (picker.ShowDialog() == DialogResult.OK) 32 | { 33 | foreach (var sel in picker.SelectedObjects) 34 | { 35 | Console.WriteLine(sel.Name); 36 | } 37 | } 38 | } 39 | ``` 40 | This repository contains a Visual Studio Test Project if you want a working example. 41 | -------------------------------------------------------------------------------- /Screenshots/ADPickerTester.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/Screenshots/ADPickerTester.gif -------------------------------------------------------------------------------- /Screenshots/img01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/Screenshots/img01.png -------------------------------------------------------------------------------- /Screenshots/img02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/Screenshots/img02.png -------------------------------------------------------------------------------- /Screenshots/img03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/Screenshots/img03.png -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/ADsPathsProviders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | /// 6 | /// Indicates the ADsPaths provider type of the DirectoryObjectPickerDialog. This provider affects the contents of the ADPath returned 7 | /// 8 | [Flags] 9 | public enum ADsPathsProviders 10 | { 11 | /// Default provider. 12 | Default = 0, 13 | 14 | /// 15 | /// The ADsPaths are converted to use the WinNT provider. 16 | /// 17 | /// The ADsPath string for the ADSI WinNT provider can be one of the following forms: 18 | /// 19 | ///WinNT: 20 | ///WinNT://<domain name> 21 | ///WinNT://<domain name>/<server> 22 | ///WinNT://<domain name>/<path> 23 | ///WinNT://<domain name>/<object name> 24 | ///WinNT://<domain name>/<object name>,<object class> 25 | ///WinNT://<server> 26 | ///WinNT://<server>/<object name> 27 | ///WinNT://<server>/<object name>,<object class> 28 | /// 29 | /// The domain name can be either a NETBIOS name or a DNS name. The server is the name of a specific server within the domain. The 30 | /// path is the path of on object, such as "printserver1/printer2". The object name is the name of a specific object. The object 31 | /// class is the class name of the named object. One example of this usage would be "WinNT://MyServer/JeffSmith,user". Specifying a 32 | /// class name can improve the performance of the bind operation. 33 | /// 34 | WinNT = 0x00000002, 35 | 36 | /// 37 | /// The ADsPaths are converted to use the LDAP provider. 38 | /// The Microsoft LDAP provider ADsPath requires the following format. 39 | /// LDAP://HostName[:PortNumber][/DistinguishedName] 40 | /// Further info, see http://msdn.microsoft.com/en-us/library/aa746384(v=VS.85).aspx. 41 | /// 42 | LDAP = 0x00000004, 43 | 44 | /// The ADsPaths for objects selected from this scope are converted to use the GC provider. 45 | GC = 0x00000008, 46 | 47 | /// 48 | /// The ADsPaths having an objectSid attribute are converted to the form 49 | /// 50 | ///LDAP://<SID=x> 51 | /// 52 | /// where x represents the hexadecimal digits of the objectSid attribute value. 53 | /// 54 | SIDPath = 0x00000010, 55 | 56 | /// 57 | /// The ADsPaths for down-level, well-known SID objects are an empty string unless this flag is specified (For example; 58 | /// DSOP_DOWNLEVEL_FILTER_INTERACTIVE). If this flag is specified, the paths have the form: 59 | /// WinNT://NT AUTHORITY/Interactive or WinNT://Creator owner. 60 | /// 61 | DownlevelBuiltinPath = 0x00000020, 62 | 63 | /// Use DownlevelBuiltinPath instead. 64 | [Obsolete("Use DownlevelBuiltinPath instead.")] 65 | DownlevelBuildinPath = 0x00000020 66 | } 67 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/ComInterop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | // ReSharper disable InconsistentNaming -- those names are from Windows/COM 5 | namespace Tulpep.ActiveDirectoryObjectPicker 6 | { 7 | /// The object picker dialog box. 8 | [ComImport, Guid("17D6CCD8-3B7B-11D2-B9E0-00C04FD8DBF7")] 9 | internal class DSObjectPicker 10 | { 11 | } 12 | 13 | /// The IDsObjectPicker interface is used by an application to initialize and display an object picker dialog box. 14 | [ComImport, Guid("0C87E64E-3B7A-11D2-B9E0-00C04FD8DBF7"), 15 | InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 16 | internal interface IDsObjectPicker 17 | { 18 | [PreserveSig] 19 | int Initialize(ref DSOP_INIT_INFO pInitInfo); 20 | 21 | [PreserveSig] 22 | int InvokeDialog(IntPtr HWND, out IDataObject lpDataObject); 23 | } 24 | 25 | [ComImport, Guid("e2d3ec9b-d041-445a-8f16-4748de8fb1cf"), 26 | InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 27 | internal interface IDsObjectPickerCredentials 28 | { 29 | [PreserveSig] 30 | int Initialize(ref DSOP_INIT_INFO pInitInfo); 31 | 32 | [PreserveSig] 33 | int InvokeDialog(IntPtr HWND, out IDataObject lpDataObject); 34 | 35 | [PreserveSig] 36 | int SetCredentials(string userName, string password); 37 | } 38 | 39 | /// Interface to enable data transfers 40 | [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), 41 | Guid("0000010e-0000-0000-C000-000000000046")] 42 | internal interface IDataObject 43 | { 44 | [PreserveSig] 45 | int GetData(ref FORMATETC pFormatEtc, ref STGMEDIUM b); 46 | 47 | void GetDataHere(ref FORMATETC pFormatEtc, ref STGMEDIUM b); 48 | 49 | [PreserveSig] 50 | int QueryGetData(IntPtr a); 51 | 52 | [PreserveSig] 53 | int GetCanonicalFormatEtc(IntPtr a, IntPtr b); 54 | 55 | [PreserveSig] 56 | int SetData(IntPtr a, IntPtr b, int c); 57 | 58 | [PreserveSig] 59 | int EnumFormatEtc(uint a, IntPtr b); 60 | 61 | [PreserveSig] 62 | int DAdvise(IntPtr a, uint b, IntPtr c, ref uint d); 63 | 64 | [PreserveSig] 65 | int DUnadvise(uint a); 66 | 67 | [PreserveSig] 68 | int EnumDAdvise(IntPtr a); 69 | } 70 | 71 | [ComImport, Guid("9068270b-0939-11d1-8be1-00c04fd8d503")] 72 | internal interface IAdsLargeInteger 73 | { 74 | long HighPart { get; set; } 75 | long LowPart { get; set; } 76 | } 77 | 78 | [ComImport, Guid("274fae1f-3626-11d1-a3a4-00c04fb950dc")] 79 | internal class NameTranslate 80 | { 81 | } 82 | 83 | [Guid("B1B272A3-3625-11D1-A3A4-00C04FB950DC"), 84 | #pragma warning disable CS0618 // Type or member is obsolete 85 | InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 86 | #pragma warning restore CS0618 // Type or member is obsolete 87 | internal interface IADsNameTranslate 88 | { 89 | [DispId(1)] 90 | int ChaseReferral { set; } 91 | 92 | [DispId(5)] 93 | string Get(int lnFormatType); 94 | 95 | [DispId(7)] 96 | object GetEx(int lnFormatType); 97 | 98 | [DispId(2)] 99 | void Init(int lnSetType, string bstrADsPath); 100 | 101 | [DispId(3)] 102 | void InitEx(int lnSetType, string bstrADsPath, string bstrUserID, string bstrDomain, string bstrPassword); 103 | 104 | [DispId(4)] 105 | void Set(int lnSetType, string bstrADsPath); 106 | 107 | [DispId(6)] 108 | void SetEx(int lnFormatType, object pVar); 109 | } 110 | } 111 | 112 | // ReSharper restore InconsistentNaming -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/DirectoryObject.cs: -------------------------------------------------------------------------------- 1 | namespace Tulpep.ActiveDirectoryObjectPicker 2 | { 3 | /// Details of a directory object selected in the DirectoryObjectPickerDialog. 4 | public class DirectoryObject 5 | { 6 | /// Initializes a new instance of the class. 7 | /// The directory object's relative distinguished name (RDN). 8 | /// The Active Directory path for this directory object. 9 | /// The name of the schema class for this directory object (objectClass attribute). 10 | /// The objects user principal name (userPrincipalName attribute). 11 | /// The attributes retrieved by the object picker as it makes the selection. 12 | public DirectoryObject(string name, string path, string schemaClass, string upn, object[] fetchedAttributes) 13 | { 14 | this.Name = name; 15 | this.Path = path; 16 | this.SchemaClassName = schemaClass; 17 | this.Upn = upn; 18 | this.FetchedAttributes = fetchedAttributes; 19 | } 20 | 21 | /// Gets attributes retrieved by the object picker as it makes the selection. 22 | public object[] FetchedAttributes { get; private set; } 23 | 24 | /// Gets the directory object's relative distinguished name (RDN). 25 | public string Name { get; private set; } 26 | 27 | /// Gets the Active Directory path for this directory object. 28 | /// 29 | /// 30 | /// The format of this string depends on the options specified in the DirectoryObjectPickerDialog from which this object was selected. 31 | /// 32 | /// 33 | public string Path { get; private set; } 34 | 35 | /// Gets the name of the schema class for this directory object (objectClass attribute). 36 | public string SchemaClassName { get; private set; } 37 | 38 | /// Gets the objects user principal name (userPrincipalName attribute). 39 | /// 40 | /// If the object does not have a userPrincipalName value, this property is an empty string. 41 | /// 42 | public string Upn { get; private set; } 43 | } 44 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/DirectoryObjectPickerDialog.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2004 Armand du Plessis http://dotnet.org.za/armand/articles/2453.aspx Thanks to Joe Cataford and 2 | // Friedrich Brunzema. Also see MSDN: http://msdn2.microsoft.com/en-us/library/ms675899.aspx Enhancements by sgryphon@computer.org (PACOM): 3 | // - Integrated with the CommonDialog API, e.g. returns a DialogResult, changed namespace, etc. 4 | // - Marked COM interop code as internal; only the main dialog (and related classes) are public. 5 | // - Added basic scope (location) and filter (object type) control, plus multi-select flag. 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.ObjectModel; 10 | using System.ComponentModel; 11 | using System.Runtime.InteropServices; 12 | using System.Windows.Forms; 13 | 14 | namespace Tulpep.ActiveDirectoryObjectPicker 15 | { 16 | /// Represents a common dialog that allows a user to select directory objects. 17 | /// 18 | /// 19 | /// The directory object picker dialog box enables a user to select one or more objects from either the global catalog, a Microsoft 20 | /// Windows 2000 domain or computer, a Microsoft Windows NT 4.0 domain or computer, or a workgroup. The object types from which a user 21 | /// can select include user, contact, group, and computer objects. 22 | /// 23 | /// This managed class wraps the Directory Object Picker common dialog from the Active Directory UI. 24 | /// 25 | /// It simplifies the scope (Locations) and filter (ObjectTypes) selection by allowing a single filter to be specified which applies to 26 | /// all scopes (translating to both up-level and down-level filter flags as necessary). 27 | /// 28 | /// 29 | /// The object type filter is also simplified by combining different types of groups (local, global, etc) and not using individual well 30 | /// known types in down-level scopes (only all well known types can be specified). 31 | /// 32 | /// 33 | /// The scope location is also simplified by combining down-level and up-level variations into a single locations flag, e.g. external domains. 34 | /// 35 | /// 36 | [DefaultProperty(nameof(DefaultObjectTypes))] 37 | public class DirectoryObjectPickerDialog : CommonDialog 38 | { 39 | private DirectoryObject[] selectedObjects; 40 | private string userName, password; 41 | 42 | /// Constructor. Sets all properties to their default values. 43 | /// 44 | /// The default values for the DirectoryObjectPickerDialog properties are: 45 | /// 46 | /// 47 | /// 48 | /// Property 49 | /// Default value 50 | /// 51 | /// 52 | /// AllowedLocations 53 | /// All locations. 54 | /// 55 | /// 56 | /// AllowedObjectTypes 57 | /// All object types. 58 | /// 59 | /// 60 | /// DefaultLocations 61 | /// None. (Will default to first location.) 62 | /// 63 | /// 64 | /// DefaultObjectTypes 65 | /// All object types. 66 | /// 67 | /// 68 | /// Providers 69 | /// . 70 | /// 71 | /// 72 | /// MultiSelect 73 | /// false. 74 | /// 75 | /// 76 | /// SkipDomainControllerCheck 77 | /// false. 78 | /// 79 | /// 80 | /// AttributesToFetch 81 | /// Empty list. 82 | /// 83 | /// 84 | /// SelectedObject 85 | /// null. 86 | /// 87 | /// 88 | /// SelectedObjects 89 | /// Empty array. 90 | /// 91 | /// 92 | /// ShowAdvancedView 93 | /// false. 94 | /// 95 | /// 96 | /// TargetComputer 97 | /// null. 98 | /// 99 | /// 100 | /// 101 | /// 102 | public DirectoryObjectPickerDialog() => ResetInner(); 103 | 104 | /// Gets or sets the scopes the DirectoryObjectPickerDialog is allowed to search. 105 | [Category("Behavior"), DefaultValue(typeof(Locations), "All"), Description("The scopes the dialog is allowed to search.")] 106 | public Locations AllowedLocations { get; set; } 107 | 108 | /// Gets or sets the types of objects the DirectoryObjectPickerDialog is allowed to search for. 109 | [Category("Behavior"), DefaultValue(typeof(ObjectTypes), "All"), Description("The types of objects the dialog is allowed to search for.")] 110 | public ObjectTypes AllowedObjectTypes { get; set; } 111 | 112 | /// A list of LDAP attribute names that will be retrieved for picked objects. 113 | [Category("Behavior"), Description("A list of LDAP attribute names that will be retrieved for picked objects.")] 114 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 115 | public Collection AttributesToFetch { get; set; } 116 | 117 | /// Gets or sets the initially selected scope in the DirectoryObjectPickerDialog. 118 | [Category("Behavior"), DefaultValue(typeof(Locations), "None"), Description("The initially selected scope in the dialog.")] 119 | public Locations DefaultLocations { get; set; } 120 | 121 | /// Gets or sets the initially seleted types of objects in the DirectoryObjectPickerDialog. 122 | [Category("Behavior"), DefaultValue(typeof(ObjectTypes), "All"), Description("The initially selected types of objects in the dialog.")] 123 | public ObjectTypes DefaultObjectTypes { get; set; } 124 | 125 | /// Gets or sets whether the user can select multiple objects. 126 | /// 127 | /// If this flag is false, the user can select only one object. 128 | /// 129 | [Category("Behavior"), DefaultValue(false), Description("Whether the user can select multiple objects.")] 130 | public bool MultiSelect { get; set; } 131 | 132 | /// Gets or sets the providers affecting the ADPath returned in objects. 133 | [Category("Behavior"), DefaultValue(typeof(ADsPathsProviders), "Default"), Description("The providers affecting the ADPath returned in objects.")] 134 | public ADsPathsProviders Providers { get; set; } 135 | 136 | /// Gets the directory object selected in the dialog, or null if no object was selected. 137 | /// 138 | /// 139 | /// If MultiSelect is enabled, then this property returns only the first selected object. Use SelectedObjects to get an array of all 140 | /// objects selected. 141 | /// 142 | /// 143 | [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 144 | public DirectoryObject SelectedObject => selectedObjects == null || selectedObjects.Length == 0 ? null : selectedObjects[0]; 145 | 146 | /// Gets an array of the directory objects selected in the dialog. 147 | [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 148 | public DirectoryObject[] SelectedObjects => selectedObjects == null ? (new DirectoryObject[0]) : (DirectoryObject[])selectedObjects.Clone(); 149 | 150 | /// Gets or sets whether objects flagged as show in advanced view only are displayed (up-level). 151 | [Category("Appearance"), DefaultValue(false), Description("Whether objects flagged as show in advanced view only are displayed (up-level).")] 152 | public bool ShowAdvancedView { get; set; } 153 | 154 | /// Gets or sets the whether to check whether the target is a Domain Controller and hide the "Local Computer" scope 155 | /// 156 | /// 157 | /// From MSDN: 158 | /// 159 | /// If this flag is NOT set, then the DSOP_SCOPE_TYPE_TARGET_COMPUTER flag will be ignored if the target computer is a DC. This flag 160 | /// has no effect unless DSOP_SCOPE_TYPE_TARGET_COMPUTER is specified. 161 | /// 162 | /// 163 | [Category("Behavior"), DefaultValue(false), Description("Whether to check whether the target is a Domain Controller and hide the 'Local Computer' scope.")] 164 | public bool SkipDomainControllerCheck { get; set; } 165 | 166 | /// Gets or sets the name of the target computer. 167 | /// 168 | /// 169 | /// The dialog box operates as if it is running on the target computer, using the target computer to determine the joined domain and 170 | /// enterprise. If this value is null or empty, the target computer is the local computer. 171 | /// 172 | /// 173 | [Category("Behavior"), DefaultValue(null), Description("The name of the target computer.")] 174 | public string TargetComputer { get; set; } 175 | 176 | /// Resets all properties to their default values. 177 | public override void Reset() => ResetInner(); 178 | 179 | /// Use this method to override the user credentials, passing new credentials for the account profile to be used. 180 | /// Name of the user. 181 | /// The password for the user. 182 | public void SetCredentials(string userName, string password) 183 | { 184 | this.userName = userName; 185 | this.password = password; 186 | } 187 | 188 | /// Displays the Directory Object Picker (Active Directory) common dialog, when called by ShowDialog. 189 | /// Handle to the window that owns the dialog. 190 | /// 191 | /// If the user clicks the OK button of the Directory Object Picker dialog that is displayed, true is returned; otherwise, false. 192 | /// 193 | protected override bool RunDialog(IntPtr hwndOwner) 194 | { 195 | var ipicker = Initialize(); 196 | if (ipicker == null) 197 | { 198 | selectedObjects = null; 199 | return false; 200 | } 201 | 202 | try 203 | { 204 | IDataObject dataObj = null; 205 | var hresult = ipicker.InvokeDialog(hwndOwner, out dataObj); 206 | if (hresult == HRESULT.S_OK) 207 | { 208 | selectedObjects = ProcessSelections(dataObj); 209 | Marshal.ReleaseComObject(dataObj); 210 | return true; 211 | } 212 | if (hresult == HRESULT.S_FALSE) 213 | { 214 | selectedObjects = null; 215 | return false; 216 | } 217 | throw new COMException("IDsObjectPicker.InvokeDialog failed", hresult); 218 | } 219 | finally 220 | { 221 | Marshal.ReleaseComObject(ipicker); 222 | } 223 | } 224 | 225 | // Convert Locations to DSOP_SCOPE_TYPE_FLAGS 226 | private static uint GetScope(Locations locations) 227 | { 228 | uint scope = 0; 229 | if (locations.IsFlagSet(Locations.LocalComputer)) 230 | { 231 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_TARGET_COMPUTER; 232 | } 233 | if (locations.IsFlagSet(Locations.JoinedDomain)) 234 | { 235 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN | 236 | DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN; 237 | } 238 | if (locations.IsFlagSet(Locations.EnterpriseDomain)) 239 | { 240 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN; 241 | } 242 | if (locations.IsFlagSet(Locations.GlobalCatalog)) 243 | { 244 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_GLOBAL_CATALOG; 245 | } 246 | if (locations.IsFlagSet(Locations.ExternalDomain)) 247 | { 248 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_EXTERNAL_DOWNLEVEL_DOMAIN | 249 | DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_EXTERNAL_UPLEVEL_DOMAIN; 250 | } 251 | if (locations.IsFlagSet(Locations.Workgroup)) 252 | { 253 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_WORKGROUP; 254 | } 255 | if (locations.IsFlagSet(Locations.UserEntered)) 256 | { 257 | scope |= DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_USER_ENTERED_DOWNLEVEL_SCOPE | 258 | DSOP_SCOPE_TYPE_FLAGS.DSOP_SCOPE_TYPE_USER_ENTERED_UPLEVEL_SCOPE; 259 | } 260 | return scope; 261 | } 262 | 263 | // Convert ObjectTypes to DSCOPE_SCOPE_INIT_INFO_FLAGS 264 | private uint GetDefaultFilter() 265 | { 266 | uint defaultFilter = 0; 267 | if (DefaultObjectTypes.IsFlagSet(ObjectTypes.Users) || DefaultObjectTypes.IsFlagSet(ObjectTypes.WellKnownPrincipals)) 268 | { 269 | defaultFilter |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_DEFAULT_FILTER_USERS; 270 | } 271 | if (DefaultObjectTypes.IsFlagSet(ObjectTypes.Groups) || DefaultObjectTypes.IsFlagSet(ObjectTypes.BuiltInGroups)) 272 | { 273 | defaultFilter |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_DEFAULT_FILTER_GROUPS; 274 | } 275 | if (DefaultObjectTypes.IsFlagSet(ObjectTypes.Computers)) 276 | { 277 | defaultFilter |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_DEFAULT_FILTER_COMPUTERS; 278 | } 279 | if (DefaultObjectTypes.IsFlagSet(ObjectTypes.Contacts)) 280 | { 281 | defaultFilter |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_DEFAULT_FILTER_CONTACTS; 282 | } 283 | if (DefaultObjectTypes.IsFlagSet(ObjectTypes.ServiceAccounts)) 284 | { 285 | defaultFilter |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_DEFAULT_FILTER_SERVICE_ACCOUNTS; 286 | } 287 | return defaultFilter; 288 | } 289 | 290 | // Convert ObjectTypes to DSOP_DOWNLEVEL_FLAGS 291 | private uint GetDownLevelFilter() 292 | { 293 | uint downlevelFilter = 0; 294 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Users)) 295 | { 296 | downlevelFilter |= DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_USERS; 297 | } 298 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Groups)) 299 | { 300 | downlevelFilter |= DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS | 301 | DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_GLOBAL_GROUPS; 302 | } 303 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Computers)) 304 | { 305 | downlevelFilter |= DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_COMPUTERS; 306 | } 307 | // Contacts not available in downlevel scopes 308 | //if ((allowedTypes & ObjectTypes.Contacts) == ObjectTypes.Contacts) 309 | // Exclude build in groups if not selected 310 | if ((AllowedObjectTypes & ObjectTypes.BuiltInGroups) == 0) 311 | { 312 | downlevelFilter |= DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_EXCLUDE_BUILTIN_GROUPS; 313 | } 314 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.WellKnownPrincipals)) 315 | { 316 | downlevelFilter |= DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_ALL_WELLKNOWN_SIDS; 317 | // This includes all the following: 318 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_WORLD | 319 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_AUTHENTICATED_USER | 320 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_ANONYMOUS | 321 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_BATCH | 322 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_CREATOR_OWNER | 323 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_CREATOR_GROUP | 324 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_DIALUP | 325 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_INTERACTIVE | 326 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_NETWORK | 327 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_SERVICE | 328 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_SYSTEM | 329 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_TERMINAL_SERVER | 330 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_LOCAL_SERVICE | 331 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_NETWORK_SERVICE | 332 | //DSOP_DOWNLEVEL_FLAGS.DSOP_DOWNLEVEL_FILTER_REMOTE_LOGON; 333 | } 334 | return downlevelFilter; 335 | } 336 | 337 | private object[] GetFetchedAttributes(IntPtr pvarFetchedAttributes, int cFetchedAttributes, string schemaClassName) 338 | { 339 | var fetchedAttributes = cFetchedAttributes > 0 ? Marshal.GetObjectsForNativeVariants(pvarFetchedAttributes, cFetchedAttributes) : (new object[0]); 340 | for (var i = 0; i < fetchedAttributes.Length; i++) 341 | { 342 | if (fetchedAttributes[i] is IAdsLargeInteger largeInteger) 343 | { 344 | var l = largeInteger.HighPart * 0x100000000L + (uint)largeInteger.LowPart; 345 | fetchedAttributes[i] = l; 346 | } 347 | 348 | if (AttributesToFetch[i].Equals("objectClass", StringComparison.OrdinalIgnoreCase)) // see comments in Initialize() function 349 | fetchedAttributes[i] = schemaClassName; 350 | } 351 | 352 | return fetchedAttributes; 353 | } 354 | 355 | // Convert ADsPathsProviders to DSOP_SCOPE_INIT_INFO_FLAGS 356 | private uint GetProviderFlags() 357 | { 358 | uint scope = 0; 359 | if (Providers.IsFlagSet(ADsPathsProviders.WinNT)) 360 | scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT; 361 | 362 | if (Providers.IsFlagSet(ADsPathsProviders.LDAP)) 363 | scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_LDAP; 364 | 365 | if (Providers.IsFlagSet(ADsPathsProviders.GC)) 366 | scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_PROVIDER_GC; 367 | 368 | if (Providers.IsFlagSet(ADsPathsProviders.SIDPath)) 369 | scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_SID_PATH; 370 | 371 | if (Providers.IsFlagSet(ADsPathsProviders.DownlevelBuiltinPath)) 372 | scope |= DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_WANT_DOWNLEVEL_BUILTIN_PATH; 373 | 374 | return scope; 375 | } 376 | 377 | // Convert ObjectTypes to DSOP_FILTER_FLAGS_FLAGS 378 | private uint GetUpLevelFilter() 379 | { 380 | uint uplevelFilter = 0; 381 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Users)) 382 | { 383 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_USERS; 384 | } 385 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Groups)) 386 | { 387 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_UNIVERSAL_GROUPS_DL | 388 | DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_UNIVERSAL_GROUPS_SE | 389 | DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_GLOBAL_GROUPS_DL | 390 | DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_GLOBAL_GROUPS_SE | 391 | DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_DOMAIN_LOCAL_GROUPS_DL | 392 | DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_DOMAIN_LOCAL_GROUPS_SE; 393 | } 394 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Computers)) 395 | { 396 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_COMPUTERS; 397 | } 398 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.Contacts)) 399 | { 400 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_CONTACTS; 401 | } 402 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.BuiltInGroups)) 403 | { 404 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_BUILTIN_GROUPS; 405 | } 406 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.WellKnownPrincipals)) 407 | { 408 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_WELL_KNOWN_PRINCIPALS; 409 | } 410 | if (AllowedObjectTypes.IsFlagSet(ObjectTypes.ServiceAccounts)) 411 | { 412 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_SERVICE_ACCOUNTS; 413 | } 414 | if (ShowAdvancedView) 415 | { 416 | uplevelFilter |= DSOP_FILTER_FLAGS_FLAGS.DSOP_FILTER_INCLUDE_ADVANCED_VIEW; 417 | } 418 | return uplevelFilter; 419 | } 420 | 421 | private IDsObjectPicker Initialize() 422 | { 423 | var picker = new DSObjectPicker(); 424 | var ipicker = (IDsObjectPicker)picker; 425 | 426 | var scopeInitInfoList = new List(); 427 | 428 | // Note the same default and filters are used by all scopes 429 | var defaultFilter = GetDefaultFilter(); 430 | var upLevelFilter = GetUpLevelFilter(); 431 | var downLevelFilter = GetDownLevelFilter(); 432 | var providerFlags = GetProviderFlags(); 433 | 434 | // Internall, use one scope for the default (starting) locations. 435 | var startingScope = GetScope(DefaultLocations); 436 | if (startingScope > 0) 437 | { 438 | var startingScopeInfo = new DSOP_SCOPE_INIT_INFO 439 | { 440 | cbSize = (uint)Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO)), 441 | flType = startingScope, 442 | flScope = DSOP_SCOPE_INIT_INFO_FLAGS.DSOP_SCOPE_FLAG_STARTING_SCOPE | defaultFilter | providerFlags, 443 | pwzADsPath = null, 444 | pwzDcName = null, 445 | hr = 0, 446 | }; 447 | startingScopeInfo.FilterFlags.Uplevel.flBothModes = upLevelFilter; 448 | startingScopeInfo.FilterFlags.flDownlevel = downLevelFilter; 449 | scopeInitInfoList.Add(startingScopeInfo); 450 | } 451 | 452 | // And another scope for all other locations (AllowedLocation values not in DefaultLocation) 453 | var otherLocations = AllowedLocations & (~DefaultLocations); 454 | var otherScope = GetScope(otherLocations); 455 | if (otherScope > 0) 456 | { 457 | var otherScopeInfo = new DSOP_SCOPE_INIT_INFO 458 | { 459 | cbSize = (uint)Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO)), 460 | flType = otherScope, 461 | flScope = defaultFilter | providerFlags, 462 | pwzADsPath = null, 463 | pwzDcName = null, 464 | hr = 0 465 | }; 466 | otherScopeInfo.FilterFlags.Uplevel.flBothModes = upLevelFilter; 467 | otherScopeInfo.FilterFlags.flDownlevel = downLevelFilter; 468 | scopeInitInfoList.Add(otherScopeInfo); 469 | } 470 | 471 | var scopeInitInfo = scopeInitInfoList.ToArray(); 472 | 473 | // TODO: Scopes for alternate ADs, alternate domains, alternate computers, etc 474 | 475 | // Allocate memory from the unmananged mem of the process, this should be freed later!?? 476 | var refScopeInitInfo = Marshal.AllocHGlobal 477 | (Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO)) * scopeInitInfo.Length); 478 | 479 | // Marshal structs to pointers 480 | for (var index = 0; index < scopeInitInfo.Length; index++) 481 | { 482 | Marshal.StructureToPtr(scopeInitInfo[index], 483 | refScopeInitInfo.OffsetWith(index * Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO))), 484 | false); 485 | } 486 | 487 | // Initialize structure with data to initialize an object picker dialog box. 488 | var initInfo = new DSOP_INIT_INFO 489 | { 490 | cbSize = (uint)Marshal.SizeOf(typeof(DSOP_INIT_INFO)), 491 | pwzTargetComputer = TargetComputer, 492 | cDsScopeInfos = (uint)scopeInitInfo.Length, 493 | aDsScopeInfos = refScopeInitInfo, 494 | }; 495 | // Flags that determine the object picker options. 496 | uint flOptions = 0; 497 | if (MultiSelect) 498 | { 499 | flOptions |= DSOP_INIT_INFO_FLAGS.DSOP_FLAG_MULTISELECT; 500 | } 501 | // Only set DSOP_INIT_INFO_FLAGS.DSOP_FLAG_SKIP_TARGET_COMPUTER_DC_CHECK if we know target is not a DC (which then saves 502 | // initialization time). 503 | if (SkipDomainControllerCheck) 504 | { 505 | flOptions |= DSOP_INIT_INFO_FLAGS.DSOP_FLAG_SKIP_TARGET_COMPUTER_DC_CHECK; 506 | } 507 | initInfo.flOptions = flOptions; 508 | 509 | // there's a (seeming?) bug on my Windows XP when fetching the objectClass attribute - the pwzClass field is corrupted... plus, 510 | // it returns a multivalued array for this attribute. In Windows 2008 R2, however, only last value is returned, just as in 511 | // pwzClass. So won't actually be retrieving __objectClass__ - will give pwzClass instead 512 | var goingToFetch = new List(AttributesToFetch); 513 | for (var i = 0; i < goingToFetch.Count; i++) 514 | { 515 | if (goingToFetch[i].Equals("objectClass", StringComparison.OrdinalIgnoreCase)) 516 | goingToFetch[i] = "__objectClass__"; 517 | } 518 | 519 | initInfo.cAttributesToFetch = (uint)goingToFetch.Count; 520 | var unmanagedAttributesToFetch = new UnmanagedArrayOfStrings(goingToFetch); 521 | initInfo.apwzAttributeNames = unmanagedAttributesToFetch.ArrayPtr; 522 | 523 | // If the user has defined new credentials, set them now 524 | if (!string.IsNullOrEmpty(userName)) 525 | { 526 | var cred = (IDsObjectPickerCredentials)ipicker; 527 | cred.SetCredentials(userName, password); 528 | } 529 | 530 | try 531 | { 532 | // Initialize the Object Picker Dialog Box with our options 533 | var hresult = ipicker.Initialize(ref initInfo); 534 | if (hresult != HRESULT.S_OK) 535 | { 536 | Marshal.ReleaseComObject(ipicker); 537 | throw new COMException("IDsObjectPicker.Initialize failed", hresult); 538 | } 539 | return ipicker; 540 | } 541 | finally 542 | { 543 | /* 544 | from MSDN (http://msdn.microsoft.com/en-us/library/ms675899(VS.85).aspx): 545 | 546 | Initialize can be called multiple times, but only the last call has effect. 547 | Be aware that object picker makes its own copy of InitInfo. 548 | */ 549 | Marshal.FreeHGlobal(refScopeInitInfo); 550 | unmanagedAttributesToFetch.Dispose(); 551 | } 552 | } 553 | 554 | private DirectoryObject[] ProcessSelections(IDataObject dataObj) 555 | { 556 | if (dataObj == null) 557 | return null; 558 | 559 | DirectoryObject[] selections = null; 560 | 561 | // The STGMEDIUM structure is a generalized global memory handle used for data transfer operations 562 | var stg = new STGMEDIUM 563 | { 564 | tymed = (uint)TYMED.TYMED_HGLOBAL, 565 | hGlobal = IntPtr.Zero, 566 | pUnkForRelease = IntPtr.Zero 567 | }; 568 | 569 | // The FORMATETC structure is a generalized Clipboard format. 570 | var fe = new FORMATETC 571 | { 572 | cfFormat = DataFormats.GetFormat(CLIPBOARD_FORMAT.CFSTR_DSOP_DS_SELECTION_LIST).Id, 573 | // The CFSTR_DSOP_DS_SELECTION_LIST clipboard format is provided by the IDataObject obtained by calling IDsObjectPicker::InvokeDialog 574 | ptd = IntPtr.Zero, 575 | dwAspect = 1, //DVASPECT.DVASPECT_CONTENT = 1, 576 | lindex = -1, // all of the data 577 | tymed = (uint)TYMED.TYMED_HGLOBAL //The storage medium is a global memory handle (HGLOBAL) 578 | }; 579 | 580 | var hresult = dataObj.GetData(ref fe, ref stg); 581 | if (hresult != HRESULT.S_OK) throw new COMException("IDataObject.GetData failed", hresult); 582 | 583 | var pDsSL = PInvoke.GlobalLock(stg.hGlobal); 584 | if (pDsSL == IntPtr.Zero) throw new Win32Exception("GlobalLock(stg.hGlobal) failed"); 585 | 586 | try 587 | { 588 | // the start of our structure 589 | var current = pDsSL; 590 | // get the # of items selected 591 | var cnt = Marshal.ReadInt32(current); 592 | var cFetchedAttributes = Marshal.ReadInt32(current, Marshal.SizeOf(typeof(uint))); 593 | 594 | // if we selected at least 1 object 595 | if (cnt > 0) 596 | { 597 | selections = new DirectoryObject[cnt]; 598 | // increment the pointer so we can read the DS_SELECTION structure 599 | current = current.OffsetWith(Marshal.SizeOf(typeof(uint)) * 2); 600 | // now loop through the structures 601 | for (var i = 0; i < cnt; i++) 602 | { 603 | // marshal the pointer to the structure 604 | var s = (DS_SELECTION)Marshal.PtrToStructure(current, typeof(DS_SELECTION)); 605 | 606 | // increment the position of our pointer by the size of the structure 607 | current = current.OffsetWith(Marshal.SizeOf(typeof(DS_SELECTION))); 608 | 609 | var name = s.pwzName; 610 | var path = s.pwzADsPath; 611 | var schemaClassName = s.pwzClass; 612 | var upn = s.pwzUPN; 613 | var fetchedAttributes = GetFetchedAttributes(s.pvarFetchedAttributes, cFetchedAttributes, schemaClassName); 614 | 615 | selections[i] = new DirectoryObject(name, path, schemaClassName, upn, fetchedAttributes); 616 | } 617 | } 618 | } 619 | finally 620 | { 621 | PInvoke.GlobalUnlock(pDsSL); 622 | PInvoke.ReleaseStgMedium(ref stg); 623 | } 624 | return selections; 625 | } 626 | 627 | #pragma warning disable IDE0051 // Remove unused private members 628 | private void ResetAttributesToFetch() => AttributesToFetch = new Collection(); 629 | #pragma warning restore IDE0051 // Remove unused private members 630 | 631 | private void ResetInner() // can be called from constructor without a "Virtual member call in constructor" warning 632 | { 633 | AllowedLocations = Locations.All; 634 | AllowedObjectTypes = ObjectTypes.All; 635 | DefaultLocations = Locations.None; 636 | DefaultObjectTypes = ObjectTypes.All; 637 | Providers = ADsPathsProviders.Default; 638 | MultiSelect = false; 639 | SkipDomainControllerCheck = false; 640 | ResetAttributesToFetch(); 641 | selectedObjects = null; 642 | ShowAdvancedView = false; 643 | TargetComputer = null; 644 | } 645 | 646 | private bool ShouldSerializeAttributesToFetch() => AttributesToFetch != null && AttributesToFetch.Count > 0; 647 | } 648 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | internal static class EnumExtensions 6 | { 7 | /// Determines whether the enumerated flag value has the specified flag set. 8 | /// The enumerated type. 9 | /// The enumerated flag value. 10 | /// The flag value to check. 11 | /// true if is flag set; otherwise, false. 12 | public static bool IsFlagSet(this T flags, T flag) where T : struct, System.Enum 13 | { 14 | CheckHasFlags(); 15 | var flagValue = Convert.ToInt64(flag); 16 | return (Convert.ToInt64(flags) & flagValue) == flagValue; 17 | } 18 | 19 | /// Checks if represents an enumeration and throws an exception if not. 20 | /// The to validate. 21 | /// 22 | private static void CheckHasFlags() where T : struct, System.Enum 23 | { 24 | if (!IsFlags()) 25 | throw new ArgumentException($"Type '{typeof(T).FullName}' doesn't have the 'Flags' attribute"); 26 | } 27 | 28 | /// Determines whether this enumerations has the set. 29 | /// The enumerated type. 30 | /// true if this instance has the set; otherwise, false. 31 | private static bool IsFlags() where T : struct, System.Enum => Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)); 32 | } 33 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/ExtensionAttributeFor.NET_2.0.cs: -------------------------------------------------------------------------------- 1 | #if NET20 2 | namespace System.Runtime.CompilerServices 3 | { 4 | /// Attribute allowing extenders to be used with .NET Framework 2.0. 5 | internal sealed class ExtensionAttribute : Attribute 6 | { 7 | } 8 | } 9 | #endif -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/IntPtrExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | internal static class IntPtrExtensions 6 | { 7 | /// Adds an offset to the value of a pointer. 8 | /// The pointer to add the offset to. 9 | /// The offset to add. 10 | /// A new pointer that reflects the addition of to . 11 | public static IntPtr OffsetWith(this IntPtr pointer, long offset) => 12 | // On 64bits computer, we need ToInt64() to prevent exceptions when process is using more than int.MaxValue of memory. 13 | // 14 | // On 32bits computer, the use of ToInt64() has no effect except when the pointer moved by offset would make it past the 15 | // int.MaxValue barrier. We still need to fail in that case so let's make the IntPtr constructor fail with a meaningful error message. 16 | new IntPtr(pointer.ToInt64() + offset); 17 | } 18 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/Locations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | /// Indicates the scope the DirectoryObjectPickerDialog searches for objects. 6 | [Flags] 7 | public enum Locations 8 | { 9 | /// No locations. 10 | None = 0x0, 11 | 12 | /// The target computer (down-level). 13 | LocalComputer = 0x0001, 14 | 15 | /// A domain to which the target computer is joined (down-level and up-level). 16 | JoinedDomain = 0x0002, 17 | 18 | /// All Windows 2000 domains in the enterprise to which the target computer belongs (up-level). 19 | EnterpriseDomain = 0x0004, 20 | 21 | /// A scope containing objects from all domains in the enterprise (up-level). 22 | GlobalCatalog = 0x0008, 23 | 24 | /// 25 | /// All domains external to the enterprise, but trusted by the domain to which the target computer is joined (down-level and up-level). 26 | /// 27 | ExternalDomain = 0x0010, 28 | 29 | /// The workgroup to which the target computer is joined (down-level). 30 | /// 31 | /// 32 | /// Applies only if the target computer is not joined to a domain. The only type of object that can be selected from a workgroup is 33 | /// a computer. 34 | /// 35 | /// 36 | Workgroup = 0x0020, 37 | 38 | /// Enables the user to enter a scope (down-level and up-level). 39 | /// 40 | /// If not specified, the dialog box restricts the user to the scopes in the locations drop-down list. 41 | /// 42 | UserEntered = 0x0040, 43 | 44 | /// All locations. 45 | All = 0x007F 46 | } 47 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/NameTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | /// Active Directory name translation. 6 | /// 7 | /// 8 | /// Translates names between Active Directory formats, e.g. from down-level NT4 style names ("ACME\alice") to User Principal Name ("alice@acme.com"). 9 | /// 10 | /// This utility class encapsulates the ActiveDs.dll COM library. 11 | /// 12 | public static class NameTranslator 13 | { 14 | private const int NameTypeNt4 = (int)ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_NT4; 15 | private const int NameTypeUpn = (int)ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_USER_PRINCIPAL_NAME; 16 | 17 | /// Convert from a down-level NT4 style name to an Active Directory User Principal Name (UPN). 18 | public static string TranslateDownLevelToUpn(string downLevelNt4Name) 19 | { 20 | if (downLevelNt4Name == null) throw new ArgumentNullException("downLevelNt4Name"); 21 | if (downLevelNt4Name.Length == 0) throw new ArgumentOutOfRangeException("downLevelNt4Name", "downLevelNt4Name is empty"); 22 | 23 | // ReSharper disable once SuspiciousTypeConversion.Global -- COM object and interfaces 24 | var nameTranslate = (IADsNameTranslate)new NameTranslate(); 25 | nameTranslate.Set(NameTypeNt4, downLevelNt4Name); 26 | return nameTranslate.Get(NameTypeUpn); 27 | } 28 | 29 | /// Convert from an Active Directory User Principal Name (UPN) to a down-level NT4 style name. 30 | public static string TranslateUpnToDownLevel(string userPrincipalName) 31 | { 32 | if (userPrincipalName == null) throw new ArgumentNullException("userPrincipalName"); 33 | if (userPrincipalName.Length == 0) throw new ArgumentOutOfRangeException("userPrincipalName", "userPrincipalName is empty"); 34 | 35 | // ReSharper disable once SuspiciousTypeConversion.Global -- COM object and interfaces 36 | var nameTranslate = (IADsNameTranslate)new NameTranslate(); 37 | nameTranslate.Set(NameTypeUpn, userPrincipalName); 38 | return nameTranslate.Get(NameTypeNt4); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/ObjectTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tulpep.ActiveDirectoryObjectPicker 4 | { 5 | /// Indicates the type of objects the DirectoryObjectPickerDialog searches for. 6 | [Flags] 7 | public enum ObjectTypes 8 | { 9 | /// No object types. 10 | None = 0x0, 11 | 12 | /// Includes user objects. 13 | Users = 0x0001, 14 | 15 | /// Includes security groups with universal scope. 16 | /// 17 | /// In an up-level scope, this includes distribution and security groups, with universal, global and domain local scope. 18 | /// In a down-level scope, this includes local and global groups. 19 | /// 20 | Groups = 0x0002, 21 | 22 | /// Includes computer objects. 23 | Computers = 0x0004, 24 | 25 | /// Includes contact objects. 26 | Contacts = 0x0008, 27 | 28 | /// Includes built-in group objects. 29 | /// 30 | /// In an up-level scope, this includes group objects with the built-in groupType flags. 31 | /// In a down-level scope, not setting this object type excludes local built-in groups. 32 | /// 33 | BuiltInGroups = 0x0010, 34 | 35 | /// Includes all well-known security principals. 36 | /// 37 | /// In an up-level scope, this includes the contents of the Well Known Security Principals container. 38 | /// In a down-level scope, this includes all well-known SIDs. 39 | /// 40 | WellKnownPrincipals = 0x0020, 41 | 42 | /// Includes all service accounts and group managed service accounts. 43 | ServiceAccounts = 0x0040, 44 | 45 | /// All object types. 46 | All = 0x007F 47 | } 48 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/PInvoke.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Tulpep.ActiveDirectoryObjectPicker 5 | { 6 | internal class PInvoke 7 | { 8 | /// 9 | /// The GlobalLock function locks a global memory object and returns a pointer to the first byte of the object's memory block. 10 | /// GlobalLock function increments the lock count by one. Needed for the clipboard functions when getting the data from IDataObject 11 | /// 12 | /// 13 | /// 14 | [DllImport("Kernel32.dll", SetLastError = true)] 15 | public static extern IntPtr GlobalLock(IntPtr hMem); 16 | 17 | /// The GlobalUnlock function decrements the lock count associated with a memory object. 18 | /// 19 | /// 20 | [DllImport("Kernel32.dll", SetLastError = true)] 21 | public static extern bool GlobalUnlock(IntPtr hMem); 22 | 23 | /// Frees the specified storage medium. 24 | /// Pointer to the storage medium that is to be freed. 25 | [DllImport("ole32.dll")] 26 | internal static extern void ReleaseStgMedium([In] ref STGMEDIUM pmedium); 27 | } 28 | } -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/StructsFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | // ReSharper disable InconsistentNaming -- those names are from Windows/COM 5 | namespace Tulpep.ActiveDirectoryObjectPicker 6 | { 7 | /// Directory name types for use with IADsNameTranslate 8 | internal enum ADS_NAME_TYPE_ENUM 9 | { 10 | ADS_NAME_TYPE_1779 = 1, 11 | ADS_NAME_TYPE_CANONICAL = 2, 12 | ADS_NAME_TYPE_NT4 = 3, 13 | ADS_NAME_TYPE_DISPLAY = 4, 14 | ADS_NAME_TYPE_DOMAIN_SIMPLE = 5, 15 | ADS_NAME_TYPE_ENTERPRISE_SIMPLE = 6, 16 | ADS_NAME_TYPE_GUID = 7, 17 | ADS_NAME_TYPE_UNKNOWN = 8, 18 | ADS_NAME_TYPE_USER_PRINCIPAL_NAME = 9, 19 | ADS_NAME_TYPE_CANONICAL_EX = 10, 20 | ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME = 11, 21 | ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12, 22 | } 23 | 24 | /// The DVASPECT enumeration values specify the desired data or view aspect of the object when drawing or getting data. 25 | internal enum DVASPECT 26 | { 27 | DVASPECT_CONTENT = 1, 28 | DVASPECT_THUMBNAIL = 2, 29 | DVASPECT_ICON = 4, 30 | DVASPECT_DOCPRINT = 8 31 | } 32 | 33 | /// The TYMED enumeration values indicate the type of storage medium being used in a data transfer. 34 | internal enum TYMED 35 | { 36 | TYMED_HGLOBAL = 1, 37 | TYMED_FILE = 2, 38 | TYMED_ISTREAM = 4, 39 | TYMED_ISTORAGE = 8, 40 | TYMED_GDI = 16, 41 | TYMED_MFPICT = 32, 42 | TYMED_ENHMF = 64, 43 | TYMED_NULL = 0 44 | } 45 | 46 | /// 47 | /// The DS_SELECTION structure contains data about an object the user selected from an object picker dialog box. The DS_SELECTION_LIST 48 | /// structure contains an array of DS_SELECTION structures. 49 | /// 50 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 51 | internal struct DS_SELECTION 52 | { 53 | [MarshalAs(UnmanagedType.LPWStr)] 54 | public string pwzName; 55 | 56 | [MarshalAs(UnmanagedType.LPWStr)] 57 | public string pwzADsPath; 58 | 59 | [MarshalAs(UnmanagedType.LPWStr)] 60 | public string pwzClass; 61 | 62 | [MarshalAs(UnmanagedType.LPWStr)] 63 | public string pwzUPN; 64 | 65 | public IntPtr pvarFetchedAttributes; 66 | public uint flScopeType; 67 | } 68 | 69 | /// 70 | /// The DS_SELECTION_LIST structure contains data about the objects the user selected from an object picker dialog box. This structure 71 | /// is supplied by the IDataObject interface supplied by the IDsObjectPicker::InvokeDialog method in the CFSTR_DSOP_DS_SELECTION_LIST 72 | /// data format. 73 | /// 74 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 75 | internal struct DS_SELECTION_LIST 76 | { 77 | public uint cItems; 78 | public uint cFetchedAttributes; 79 | public DS_SELECTION[] aDsSelection; 80 | } 81 | 82 | /// 83 | /// The DSOP_FILTER_FLAGS structure contains flags that indicate the types of objects presented to the user for a specified scope or scopes. 84 | /// 85 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 86 | internal struct DSOP_FILTER_FLAGS 87 | { 88 | public DSOP_UPLEVEL_FILTER_FLAGS Uplevel; 89 | public uint flDownlevel; 90 | } 91 | 92 | /// The DSOP_INIT_INFO structure contains data required to initialize an object picker dialog box. 93 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 94 | internal struct DSOP_INIT_INFO 95 | { 96 | public uint cbSize; 97 | 98 | [MarshalAs(UnmanagedType.LPWStr)] 99 | public string pwzTargetComputer; 100 | 101 | public uint cDsScopeInfos; 102 | public IntPtr aDsScopeInfos; 103 | public uint flOptions; 104 | public uint cAttributesToFetch; 105 | public IntPtr apwzAttributeNames; 106 | } 107 | 108 | /// 109 | /// The DSOP_SCOPE_INIT_INFO structure describes one or more scope types that have the same attributes. A scope type is a type of 110 | /// location, for example a domain, computer, or Global Catalog, from which the user can select objects. 111 | /// 112 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto), Serializable] 113 | internal struct DSOP_SCOPE_INIT_INFO 114 | { 115 | public uint cbSize; 116 | public uint flType; 117 | public uint flScope; 118 | public DSOP_FILTER_FLAGS FilterFlags; 119 | 120 | [MarshalAs(UnmanagedType.LPWStr)] 121 | public string pwzDcName; 122 | 123 | [MarshalAs(UnmanagedType.LPWStr)] 124 | public string pwzADsPath; 125 | 126 | public uint hr; 127 | } 128 | 129 | /// 130 | /// The DSOP_UPLEVEL_FILTER_FLAGS structure contains flags that indicate the filters to use for an up-level scope. An up-level scope is 131 | /// a scope that supports the ADSI LDAP provider. 132 | /// 133 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 134 | internal struct DSOP_UPLEVEL_FILTER_FLAGS 135 | { 136 | public uint flBothModes; 137 | public uint flMixedModeOnly; 138 | public uint flNativeModeOnly; 139 | } 140 | 141 | /// This structure is used as a parameter in OLE functions and methods that require data format information. 142 | [StructLayout(LayoutKind.Sequential)] 143 | internal struct FORMATETC 144 | { 145 | public int cfFormat; 146 | public IntPtr ptd; 147 | public uint dwAspect; 148 | public int lindex; 149 | public uint tymed; 150 | } 151 | 152 | /// The STGMEDIUM structure is a generalized global memory handle used for data transfer operations by the IDataObject 153 | [StructLayout(LayoutKind.Sequential)] 154 | internal struct STGMEDIUM 155 | { 156 | public uint tymed; 157 | public IntPtr hGlobal; 158 | /* Presumably this is supposed to be an Object but according to a comment by xC0000005 on the 159 | * DSOP_INIT_INFO MSDN page, there is a bug in Windows whereby the returned object doesn't 160 | * support IUnknown, causing a E_NOT_IMPL error from .NET. 161 | * 162 | * Changing it to IntPtr makes it opaque to .NET and prevents the error 163 | */ 164 | public IntPtr pUnkForRelease; 165 | } 166 | 167 | /// The CFSTR_DSOP_DS_SELECTION_LIST clipboard format is provided by the IDataObject obtained by calling IDsObjectPicker.InvokeDialog 168 | internal class CLIPBOARD_FORMAT 169 | { 170 | public const string CFSTR_DSOP_DS_SELECTION_LIST = 171 | "CFSTR_DSOP_DS_SELECTION_LIST"; 172 | } 173 | 174 | /// Contains the filter flags to use for down-level scopes 175 | internal class DSOP_DOWNLEVEL_FLAGS 176 | { 177 | public const uint DSOP_DOWNLEVEL_FILTER_ALL_WELLKNOWN_SIDS = 0x80020000; 178 | public const uint DSOP_DOWNLEVEL_FILTER_ANONYMOUS = 0x80000040; 179 | public const uint DSOP_DOWNLEVEL_FILTER_AUTHENTICATED_USER = 0x80000020; 180 | public const uint DSOP_DOWNLEVEL_FILTER_BATCH = 0x80000080; 181 | public const uint DSOP_DOWNLEVEL_FILTER_COMPUTERS = 0x80000008; 182 | public const uint DSOP_DOWNLEVEL_FILTER_CREATOR_GROUP = 0x80000200; 183 | public const uint DSOP_DOWNLEVEL_FILTER_CREATOR_OWNER = 0x80000100; 184 | public const uint DSOP_DOWNLEVEL_FILTER_DIALUP = 0x80000400; 185 | public const uint DSOP_DOWNLEVEL_FILTER_EXCLUDE_BUILTIN_GROUPS = 0x80008000; 186 | public const uint DSOP_DOWNLEVEL_FILTER_GLOBAL_GROUPS = 0x80000004; 187 | public const uint DSOP_DOWNLEVEL_FILTER_IIS_APP_POOL = 0x84000000; 188 | public const uint DSOP_DOWNLEVEL_FILTER_INTERACTIVE = 0x80000800; 189 | public const uint DSOP_DOWNLEVEL_FILTER_INTERNET_USER = 0x80200000; 190 | public const uint DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS = 0x80000002; 191 | public const uint DSOP_DOWNLEVEL_FILTER_LOCAL_LOGON = 0x81000000; 192 | public const uint DSOP_DOWNLEVEL_FILTER_LOCAL_SERVICE = 0x80040000; 193 | public const uint DSOP_DOWNLEVEL_FILTER_NETWORK = 0x80001000; 194 | public const uint DSOP_DOWNLEVEL_FILTER_NETWORK_SERVICE = 0x80080000; 195 | public const uint DSOP_DOWNLEVEL_FILTER_OWNER_RIGHTS = 0x80400000; 196 | public const uint DSOP_DOWNLEVEL_FILTER_REMOTE_LOGON = 0x80100000; 197 | public const uint DSOP_DOWNLEVEL_FILTER_SERVICE = 0x80002000; 198 | public const uint DSOP_DOWNLEVEL_FILTER_SERVICES = 0x80800000; 199 | public const uint DSOP_DOWNLEVEL_FILTER_SYSTEM = 0x80004000; 200 | public const uint DSOP_DOWNLEVEL_FILTER_TERMINAL_SERVER = 0x80010000; 201 | public const uint DSOP_DOWNLEVEL_FILTER_THIS_ORG_CERT = 0x82000000; 202 | public const uint DSOP_DOWNLEVEL_FILTER_USERS = 0x80000001; 203 | public const uint DSOP_DOWNLEVEL_FILTER_WORLD = 0x80000010; 204 | } 205 | 206 | /// Filter flags to use for an up-level scope, regardless of whether it is a mixed or native mode domain. 207 | internal class DSOP_FILTER_FLAGS_FLAGS 208 | { 209 | public const uint DSOP_FILTER_BUILTIN_GROUPS = 0x00000004; 210 | public const uint DSOP_FILTER_COMPUTERS = 0x00000800; 211 | public const uint DSOP_FILTER_CONTACTS = 0x00000400; 212 | public const uint DSOP_FILTER_DOMAIN_LOCAL_GROUPS_DL = 0x00000100; 213 | public const uint DSOP_FILTER_DOMAIN_LOCAL_GROUPS_SE = 0x00000200; 214 | public const uint DSOP_FILTER_GLOBAL_GROUPS_DL = 0x00000040; 215 | public const uint DSOP_FILTER_GLOBAL_GROUPS_SE = 0x00000080; 216 | public const uint DSOP_FILTER_INCLUDE_ADVANCED_VIEW = 0x00000001; 217 | public const uint DSOP_FILTER_SERVICE_ACCOUNTS = 0x00001000; 218 | public const uint DSOP_FILTER_UNIVERSAL_GROUPS_DL = 0x00000010; 219 | public const uint DSOP_FILTER_UNIVERSAL_GROUPS_SE = 0x00000020; 220 | public const uint DSOP_FILTER_USERS = 0x00000002; 221 | public const uint DSOP_FILTER_WELL_KNOWN_PRINCIPALS = 0x00000008; 222 | } 223 | 224 | /// Flags that determine the object picker options. 225 | internal class DSOP_INIT_INFO_FLAGS 226 | { 227 | public const uint DSOP_FLAG_MULTISELECT = 0x00000001; 228 | public const uint DSOP_FLAG_SKIP_TARGET_COMPUTER_DC_CHECK = 0x00000002; 229 | } 230 | 231 | /// 232 | /// Flags that indicate the format used to return ADsPath for objects selected from this scope. The flScope member can also indicate the 233 | /// initial scope displayed in the Look in drop-down list. 234 | /// 235 | internal class DSOP_SCOPE_INIT_INFO_FLAGS 236 | { 237 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_COMPUTERS = 0x00000100; 238 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_CONTACTS = 0x00000200; 239 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_GROUPS = 0x00000080; 240 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_PASSWORDSETTINGS_OBJECTS = 0x00000800; 241 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_SERVICE_ACCOUNTS = 0x00000400; 242 | public const uint DSOP_SCOPE_FLAG_DEFAULT_FILTER_USERS = 0x00000040; 243 | public const uint DSOP_SCOPE_FLAG_STARTING_SCOPE = 0x00000001; 244 | public const uint DSOP_SCOPE_FLAG_WANT_DOWNLEVEL_BUILTIN_PATH = 0x00000020; 245 | public const uint DSOP_SCOPE_FLAG_WANT_PROVIDER_GC = 0x00000008; 246 | public const uint DSOP_SCOPE_FLAG_WANT_PROVIDER_LDAP = 0x00000004; 247 | public const uint DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT = 0x00000002; 248 | public const uint DSOP_SCOPE_FLAG_WANT_SID_PATH = 0x00000010; 249 | } 250 | 251 | /// 252 | /// Flags that indicate the scope types described by this structure. You can combine multiple scope types if all specified scopes use 253 | /// the same settings. 254 | /// 255 | internal class DSOP_SCOPE_TYPE_FLAGS 256 | { 257 | public const uint DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN = 0x00000004; 258 | public const uint DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN = 0x00000008; 259 | public const uint DSOP_SCOPE_TYPE_EXTERNAL_DOWNLEVEL_DOMAIN = 0x00000040; 260 | public const uint DSOP_SCOPE_TYPE_EXTERNAL_UPLEVEL_DOMAIN = 0x00000020; 261 | public const uint DSOP_SCOPE_TYPE_GLOBAL_CATALOG = 0x00000010; 262 | public const uint DSOP_SCOPE_TYPE_TARGET_COMPUTER = 0x00000001; 263 | public const uint DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN = 0x00000002; 264 | public const uint DSOP_SCOPE_TYPE_USER_ENTERED_DOWNLEVEL_SCOPE = 0x00000200; 265 | public const uint DSOP_SCOPE_TYPE_USER_ENTERED_UPLEVEL_SCOPE = 0x00000100; 266 | public const uint DSOP_SCOPE_TYPE_WORKGROUP = 0x00000080; 267 | } 268 | 269 | /// The IDsObjectPicker.InvokeDialog result 270 | internal class HRESULT 271 | { 272 | public const int E_NOTIMPL = unchecked((int)0x80004001); 273 | public const int S_FALSE = 1; // The user cancelled the dialog box. ppdoSelections receives NULL. ? 274 | public const int S_OK = 0; // The method succeeded. 275 | } 276 | } 277 | 278 | // ReSharper restore InconsistentNaming -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/Tulpep.ActiveDirectoryObjectPicker.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | ActiveDirectoryObjectPicker 4 | Tulpep.ActiveDirectoryObjectPicker.snk 5 | true 6 | true 7 | true 8 | Tulpep.ActiveDirectoryObjectPicker 9 | Active Directory Object Picker 10 | Armand du Plessis;Tulpep;dahall 11 | Active Directory Object Picker 12 | https://github.com/Tulpep/Active-Directory-Dialogs 13 | 14 | true 15 | Tulpep 16 | embedded 17 | WPF;Active Directory;Dialog;WinForms;Windows Forms;Picker 18 | $(AssemblyName) 19 | net20;net35;net40;net45;net452;net462;net472;net48;netcoreapp3.0;netcoreapp3.1;net5.0-windows;net6.0-windows 20 | false 21 | Tulpep.ActiveDirectoryObjectPicker 22 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 23 | 3.0.6 24 | true 25 | true 26 | snupkg 27 | MS-PL 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/Tulpep.ActiveDirectoryObjectPicker.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tulpep/Active-Directory-Object-Picker/7888679b16e929b750b6b56b3f931162fa674dc9/Tulpep.ActiveDirectoryObjectPicker/Tulpep.ActiveDirectoryObjectPicker.snk -------------------------------------------------------------------------------- /Tulpep.ActiveDirectoryObjectPicker/UnmanagedArrayOfStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Tulpep.ActiveDirectoryObjectPicker 6 | { 7 | // based on the packLPArray class. original from mailing list post by Beat Bucheli. or maybe from 8 | // http://blogs.technolog.nl/eprogrammer/archive/2005/11/24/402.aspx or maybe from somewhere else.. 9 | /// A packed array of strings. 10 | /// 11 | public sealed class UnmanagedArrayOfStrings : IDisposable 12 | { 13 | private readonly IntPtr[] _unmanagedStrings; 14 | 15 | /// Initializes a new instance of the class. 16 | /// The strings to pack. 17 | public UnmanagedArrayOfStrings(IList strings) 18 | { 19 | if (strings != null) 20 | { 21 | var length = strings.Count; 22 | _unmanagedStrings = new IntPtr[length]; 23 | var neededSize = length * IntPtr.Size; 24 | ArrayPtr = Marshal.AllocCoTaskMem(neededSize); 25 | for (var cx = length - 1; cx >= 0; cx--) 26 | { 27 | _unmanagedStrings[cx] = Marshal.StringToCoTaskMemUni(strings[cx]); 28 | Marshal.WriteIntPtr(ArrayPtr, cx * IntPtr.Size, _unmanagedStrings[cx]); 29 | } 30 | } 31 | } 32 | 33 | /// Gets the pointer to the packed array memory. 34 | public IntPtr ArrayPtr { get; private set; } 35 | 36 | /// Releases unmanaged and - optionally - managed resources. 37 | public void Dispose() 38 | { 39 | if (ArrayPtr != IntPtr.Zero) 40 | { 41 | Marshal.FreeCoTaskMem(ArrayPtr); 42 | ArrayPtr = IntPtr.Zero; 43 | } 44 | 45 | foreach (var ptr in _unmanagedStrings) 46 | { 47 | Marshal.FreeCoTaskMem(ptr); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | image: Visual Studio 2022 4 | 5 | # version format 6 | version: 3.0.{build} 7 | 8 | # Fix line endings in Windows. (runs before repo cloning) 9 | init: 10 | - git config --global core.autocrlf input 11 | 12 | 13 | # Assembly Info 14 | dotnet_csproj: 15 | patch: true 16 | file: '**\*.csproj' 17 | version: '{version}' 18 | version_prefix: '{version}' 19 | 20 | #Build Relase instead of Debug 21 | configuration: Release 22 | 23 | # Make sure all NuGet packages are installed before building. 24 | before_build: 25 | - nuget restore 26 | 27 | artifacts: 28 | - path: '**\Tulpep.ActiveDirectoryObjectPicker*.nupkg' 29 | name: Tulpep.ActiveDirectoryObjectPicker.nupkg 30 | 31 | #Publish NuGet 32 | deploy: 33 | provider: NuGet 34 | api_key: 35 | secure: 5ldn0g+rIeS2ZHbyFJjilj/nCUyUcTTMN1As3uJ3kCtW2QStDsdk4dCiL4G2KvOL 36 | artifact: /.*\.nupkg/ 37 | on: 38 | branch: master 39 | --------------------------------------------------------------------------------