How to set user "logon to" AD attribute in VB.NET - vb.net

I'm working on upgrading a solution in VB.NET that is heavily based on Active Directory. As of now, I'm trying to add a PC restriction to a new AD User upon user creation. Essentially, I need to update the Logon To attribute to include 1 or more PCs, how do I go about doing this?
I learned that I am interested in the IADsUser property "LoginWorkstations" (thanks to http://msdn.microsoft.com/en-us/library/Aa746340). As of now, I have code that can fetch this attribute from any AD user, but I cannot set it.
Here is the code I have to fetch the attribute:
Dim userADObject As new DirectoryEntry(ADPath)
Dim logonToPC as String = userADObject.InvokeGet("LoginWorkstations")(0).ToString
That will fetch the first restricted PC (if there is one) and save it in logonToPC and will look something like "PC10000"
That works great, so intuitively I would assume something like this would work:
Dim userADObject As new DirectoryEntry(ADPath)
Dim args() As Object = {"PC100001"}
userADObject.InvokeSet("LoginWorkstations", args)
But it doesn't work... It just throws a rather unhelpful exception.
I've tried testing this approach with a different attribute and it works just fine. Not much out there on Google either unfortunately...
Any help would be greatly appreciated.

You should be able to do this fairly easily - also: note that you should use the userWorkstations LDAP attribute (see note here) - this is multi-valued, e.g. it allows multiple entries.
Dim userADObject As new DirectoryEntry(ADPath)
userADObject.Properties("userWorkstations").Add("PC001")
userADObject.Properties("userWorkstations").Add("PC002")
userADObject.Properties("userWorkstations").Add("PC003")
userADObject.CommitChanges()
If you have the necessary permissions to update Active Directory, that should basically do it, I think.

Found the solution that works. I took marc_s's code and modified a bit to work properly. here's what I have:
Dim userADObject As New DirectoryEntry(Me.ADPath)
'Grab the previous restriction, because we may have to clear it first in the future
Dim priorRestriction As String = userADObject.Properties("userWorkstations").Value
If priorRestriction = "" Then
'Simply add
userADObject.Properties("userWorkstations").Add("PC001,PC002")
Else
'Important - We have to clear the old restriction before adding the new
userADObject.Properties("userWorkstations").Remove(priorRestriction)
'Now add the new restriction
userADObject.Properties("userWorkstations").Add(priorRestriction & ",PC003")
End If
'Commit!
userADObject.CommitChanges()
Something that gave me some pretty good grief what that I you can't have a space in the string being added. Example: .Add("PC001, PC002") has to be .Add("PC001,PC002")

Related

SolidWorks VBA - Translating API Help into useable code

I'd like to do what feels like a fairly simple task, and I've found the specific API Help pages which should make it clear, but, I can't actually make things work.
The Key steps that I would like to achieve are:
Rename the active document
Update References to this document to accommodate new name
Save active document.
This help page shows the Usage for renaming the doc, and under the "Remarks" heading, includes links to the next two steps, mentioning them off hand as if implementing them would be easy.
https://help.solidworks.com/2020/English/api/sldworksapi/SolidWorks.Interop.sldworks~SolidWorks.Interop.sldworks.IModelDocExtension~RenameDocument.html?verRedirect=1
The trouble is, I'm a bit of a VBA beginner - usually I get by with the 'record' function, and then tidying things up from there - but undertaking the steps above manually doesn't result in anything being recorded at all for one reason or another.
Assuming I am able to pass in the item to be renamed (I'll define a variable at the start of the Sub for this e.g. swModel = swApp.ActiveDoc), and the new name (NewName = "NEW NAME HERE"), How would I translate the Help API into a Sub that I can actually run?
Two of them suggest declaring as a Function, and one as a Public Interface - I've never used these before - do these just run in a standard Module? Do I need to write a 'master Sub' to call the different functions sequentially, or could these be included directly in the sub, if they're only to be used once?
[Feeling a little lost - it's demoralizing when the help files aren't all that helpful]
Let me know if there's any more information missing that I can add to improve my question - as I said, I'm fairly new to this coding thing...
The "record" function is sometimes a good point to start but there are a lot of functions it can't recognize while you execute them manually.
The API Help is then useful to find out how to use a specific function.
In almost every example the use of a specific method (e.g. RenameDocument) is only shown abstract. There is always a instance variable which shows you the object-type needed to call this method. So you can use these in every sub you want, but beforehand need access to the specific instance objects.
For your example the RenameDocument method is called with an object of the type IModelDocExtension. First thing for you to do is to get this object and then you can call the method as described in the help article.
Under Remarks in the article you find additional information for what you maybe have to do before or after calling a method.
For your example it is mentioned that the renaming takes permanently place after saving the document.
And finally here is what you want to do with some VBA code:
Dim swApp As SldWorks.SldWorks
Dim swModel As ModelDoc2
Sub main()
' get the solidworks application object
Set swApp = Application.SldWorks
'get the current opened document object
Set swModel = swApp.ActiveDoc
' get the modeldocextension object
Dim swModelExtension As ModelDocExtension
Set swModelExtension = swModel.Extension
Dim lRet As Long
lRet = swModelExtension.RenameDocument("NEW NAME")
If lRet = swRenameDocumentError_e.swRenameDocumentError_None Then
MsgBox "success renaming"
Else
MsgBox "failed with error: " & lRet
End If
End Sub
Afterwars you have to process the return value to check for errors described in this article: https://help.solidworks.com/2020/English/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swRenameDocumentError_e.html

Adding a reference for any user

I am struggling with references in my VBA project. In order to use my Drag and Drop function, I need to add the following reference to my workbook: Microsoft Windows Common Controls 6.0 (SP6). This reference has got the following location : C:\Windows\system32\MSCOMCTL.OCX.
I would like to make sure that for any user using my specific workbook, the reference won't be missing. I have seen many tutorials about it and could "easily" add a reference, but I would like to understand precisely what I am doing and choose the best solution. I have several questions:
Late binding, Early Biding, AddFromFile, AddFromGUI
In those two topics (1 and 2), I read many things about references. The problem is I really do not know which option fits the best to my particular case.
My reference has already been added manually. Should I code something in order to be sure it is not missing when the code runs?
About adding a reference : Which option between those four is the best? I am hesitating between AddFromFile and Late Binding.
Let's suppose I choose AddFromFile or Late Binding. I cannot manage to Print my reference's name! How do you know that Microsoft VBScript Regular Expressions 5.5's .Name is VBScript_RegExp_55? I tried the code in this topic but it does not work (Must have done something wrong...).
"Universal" Path to my reference
I am very pessimistic and I was wondering: are references always stored at the same location? Let's suppose a user uses my specific workbook and hasn't got the reference it needs for the Drag and Drop function. He/She would need to add MSCOMCTL.OCX from C:\Windows\system32\.
Are references always stored at the same location?
If not, how can I overcome that? Changing the path to something like the following?
Sub mySub()
Dim sRef, sPath As String
sRef = "MSCOMCTL.OCX"
sPath = Environ("Windir") & "\system32\" & sRef
End Sub
Thank you in advance for your answers and sorry if things are crystal clear and already answered in other posts, I may have missed it.

Lotus Notes and VBA Checking Email Address in Public Address Books

I'm trying to figure out how to check whether an e-mail address is in the public address book by using VBA. I found some code on the Web, modified it but the code gives an error 91 at line, "Set doc = view.GetAllDocumentsByKey(ChkEmailAddr)." I think that the problem has to do with the declaration type of the variable "view" or the type of view in the line "Set view = b.GetView("People\By Internet Mail")."
I'm pretty certain that I have all of the proper references activated. "Lotus Notes Domino Objects" and "Lotus Notes Automation Classes" are chosen.
I tried to get a list of views but I couldn't figure out how to do it. Do you see the error in my code or have ideas as to what I could try to do some troubleshooting?
Sub CheckEmailAddress()
Dim books As Variant
Dim view As lotus.NotesView
'Dim view As Object
Dim doc As NotesDocumentCollection
Dim dc As NotesDocument
Dim done As Variant
Dim docarr(3, 50) As Variant
Dim ChkEmailAddr As String
ChkEmailAddr = "user#example.com"
Set Session = CreateObject("Notes.Notessession")
books = Session.AddressBooks
done = False
For Each b In books
' check every public address book,
' unless we're already done
If (b.IsPublicAddressBook) Then
Debug.Print TypeName(b)
Call b.Open("", "")
Debug.Print b.Title
' look up person's last name
' in People view of address book
Set view = b.GetView("People\By Internet Mail")
Debug.Print TypeName(view)
'Debug.Print view
'Set view = b.GetView("main")
'Debug.Print TypeName(view)
Set doc = view.GetAllDocumentsByKey(ChkEmailAddr)
' if person is found, display the phone number item
'from the Person document
If Not (doc Is Nothing) Then
For j = 0 To doc.Count
docarr(0, j) = doc.GetNthDocument(j).Items(11).Text
docarr(1, j) = doc.GetNthDocument(j).Items(93).Text
docarr(2, j) = doc.GetNthDocument(j).Items(95).Text
docarr(3, j) = doc.GetNthDocument(j).Items(14).Text
Next j
End If
End If
Next b
findEmailLotus = docarr
End Sub
I'm sorry to say this, but you have a lot of issues here. Whoever wrote that code that you started from did not know what he or she was doing.
First of all, unless you are requiring that the Notes client is running while your code is running, you should be using Lotus.NotesSession instead of Notes.NotesSession. (The former corresponds to the "Lotus Notes Domino Objects", and uses COM to talk to the Notes APIs, and the latter corresponds to the "Lotus Notes Automation Classes, and uses OLE to talk to the Notes client to talk to the APIs - hence the requirement that the client must be running.)
Secondly, you haven't mentioned what version of Lotus Notes you are dealing with, but more recent versions (8 and above) include the NotesDirectory class, which includes a LoookupNames method that would probably be a better solution for you than writing your own code to loop through address books.
Third, after doing your set view operation, you really ought to be doing an If Not view is Nothing test. That will tell you whether or not you actually have a problem opening the view.
Fourth, doc is a really bad variable name for the return value from GetAllDocumentsByKey. In 20+ years of writing Notes code, I can say that anybody reading Notes code expects the variable name doc to always refer to a single document. You are getting a NotesDocumentCollection, not a single document. Do yourself a favor and change it to docs or dc, or just about anything except doc.
Fifth, using GetNthDocument in this context is usually not recommended. It performs very badly in large collections. Even worse, however, is that you are calling it four times when you could be making only one call per iteration. Instead of a For loop, consider changing it to a call to GetFirstDocument followed by While Not doc is Nothing loop that retrieves your item values and stores them in your array, and then calls getNextDocument at the bottom of the loop.
Sixth, that code referencing .Items(11), .Items(93)... that's just plain wrong. The available items within any given document are variable because Notes is schemaless. Those item numbers will refer to different fields for different people - i.e., essentially random values. That can't possibly be what you want. You should be using getFirstItem() calls with the actual names of the items that you really want to be putting in your array. You will need to study the field names used in the Domino Directory to figure this out. I recommend NotesPeek as a good tool for exploring Notes databases and/or just opening up the Domino Directory in the Domino Designer client and looking at the Person form (and associated subforms) to figure out what you need.
As to the actual error you asked about, my guess is that by adding the recommended test of If Not view Is Nothing you will gain more information, but perhaps not enough. You haven't mentioned what your debug prints are generating, but I believe there are some cases where the title is available even if the database was not successfully opened, so I don't think you should trust that as a test of whether the call worked. In fact, you really shouldn't just be doing a Call db.open("","") call. You should be doing an If db.open("","") = true to test whether it actually worked.
For people who know lotus notes, it's trival. For those that don't, there might be an easier way using web access.
Request this address
Keep your cookies
http://server/names.nsf?login&username=MYUSERNAM&password=MYPASSWORD
Then access this url and look for a 404 status or a 200 status
http://server/names.nsf/($Users)/email#domain.com?opendocument
Of course, that requires that you have web access enabled on your server and in many cases putting your password in your code is bad, and it won't work if your servers are configured in some ways.
Before coding, test it on your server.

is there a better way of retrieving my settings?

I'm not an IT professional so apologies if I've missed something obvious.
When writing a program I add a class SettingsIni that reads a text file of keys and values. I find this method really flexible as settings can be added or changed without altering any code, regardless of what application I have attached it to.
Here's the main code.
Public Shared Sub Load()
Using settingsReader As StreamReader = New StreamReader(System.AppDomain.CurrentDomain.BaseDirectory & "settings.ini")
Do While settingsReader.Peek > -1
Dim line As String = settingsReader.ReadLine
Dim keysAndValues() As String = line.Split("="c)
settingsTable.Add(keysAndValues(0).Trim, keysAndValues(1).Trim)
Loop
End Using
End Sub
Public Shared Function GetValue(ByVal key As String)
Dim value As String = settingsTable(key)
Return value
End Function
This allows you to use a setting within your code by calling the SettingsIni.GetValue method.
For example:
watcher = New FileSystemWatcher(SettingsIni.GetValue("inputDir"), "*" & SettingsIni.GetValue("extn")).
I find this makes my code esay to read.
My problem is the values in this case, inputDir and extn, are typed freehand and not checked by intellisense. I'm always worried that I may make a typo in an infrequently used branch of an application and miss it during testing.
Is there a best practice method for retrieving settings? or a way around these unchecked freehand typed values?
A best practice for your code example would be to use Constants for the possible settings.
Class Settings
Const inputDir as String = "inputDir"
Const extn as String = "extn"
End Class
watcher = New FileSystemWatcher(SettingsIni.GetValue(Settings.inputDir), "*" & SettingsIni.GetValue(Settings.extn))
I assume you are using VB.NET?
If so, there is the handy "Settings"-menu under "my project". It offers a way to store the settings for your program and retrieve them via "my.settings.YOURKEY". The advantage is, that type securtiy is enforced on this level.
Additionally, you can also store "resources" almost the same way - but resources are better suited for strings / pictures etc. But they are expecially good if you want to translate your program.
As for your current problem:
Store the path in the settings, this way you do not need to change alll your code immidiately but you can use your system and never misspell anything.
If it's a number you could do these 3 things:
Check if is numeric - using IsNumeric function
Check if it is whole number - using Int function, like: if Int(number)=number
Check for the valid range, like: if number>=lowerbound and number<=upperbound
It totally depends on you. You are the one to check almost all the things inside quotes, not the intellisense.
But you still use Try-Catch block:
Try
Dim value As String = settingsTable(key)
Return value
Catch ex As Exception
MsgBox(ex.ToString)
Return ""
End Try
So you will get an message box if you are trying to access a non-existing setting that you may have mistyped.

How do I use Google.GData.Client.AtomLinkCollection.FindService method to get the list of worksheets in a Google Spreadsheet?

I'm trying to write code that talks to Google Spreadsheets. We do a bunch of processing on our end and then pass data out to our client into this spreadsheet and I want to automate it. This seems like it should be easy.
On this page, Google says "Given a SpreadsheetEntry you've already retrieved, you can print a list of all worksheets in this spreadsheet as follows:"
AtomLink link = entry.Links.FindService(GDataSpreadsheetsNameTable.WorksheetRel, null);
WorksheetQuery query = new WorksheetQuery(link.HRef.ToString());
WorksheetFeed feed = service.Query(query);
foreach (WorksheetEntry worksheet in feed.Entries)
{
Console.WriteLine(worksheet.Title.Text);
}
Following along at home, I start with:
Dim link As AtomLink = Entry.Links.FindService(GDataSpreadsheetsNameTable.WorksheetRel, "")
Dim wsq As New WorksheetQuery(link.HRef.ToString)
and when execution gets to that second line, I find that "Object reference not set to instance of an object." The FindService method is returning nothing. And when I look at GDataSpreadsheetsNameTable.WorksheetRel, it's a constant value of "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"
I'm not really at the point where I even grok what it wants to be doing. E.g., what's a feed? Is a worksheet really what I think it is based on Excel nomenclature? That kind of stuff. But I see a couple of things that might be causing my issue:
The C# method call "...FindService(GDataSpreadsheetsNameTable.WorksheetRel, null);" I'm not sure about that null. It demands a string, so I used "" in my VB, but I'm not sure that's right.
That schemas.google.com URI doesn't seem to be live. At least, if I punch it into a browser, I get server not found. But again, I don't exactly know what it's trying to do.
So, any thoughts? Anyone have VB code that reads Google Spreadsheets and time to instruct a newbie? I'm surprised to find that there's essentially no useful sample code floating around the net.
Thanks for reading!
So, of course, right after I posted this I found some inspiration over here. Manually iterating across the collections works just fine, even if it's not the preferred way to do this. I'm still keen to hear info from others related to this, so feel encouraged to help out even though I'm maybe over this one hurdle.
For Each Entry In mySprShFeed.Entries
If Entry.Title.Text = "spreadsheetNameSought" Then
For Each link As AtomLink In Entry.Links
If link.Rel = GDataSpreadsheetsNameTable.WorksheetRel Then
Dim wsf As WorksheetFeed = service.Query(New WorksheetQuery(link.HRef.ToString))
For Each worksheet In wsf.Entries
Console.WriteLine(worksheet.Title.Text)
Next
End If
Next
End If
Next