Every now and then a question comes up on assign service principals (application registrations) to other service principals (application registrations) without creating app roles. Is that possible?
The answer is YES! It is possible.
Here is how:
]]>The request was for a multi domain forest which queries every domain controller and gets the latest lastlogon value by comparing value from each. I wrote a script and wanted to share as other might find it handy too.
]]>Now this started off as just to query for DNS Server information, but then I thought to add other pieces to get myself a good Network Inventory of all the servers in the environment.
I am utilizing the Win32_NetworkAdapterConfiguration WMI Class to get the required information.
You can modify the script below to suit your needs. The complete list of settings that can be captured:
string Caption; string Description; string SettingID; boolean ArpAlwaysSourceRoute; boolean ArpUseEtherSNAP; string DatabasePath; boolean DeadGWDetectEnabled; string DefaultIPGateway[]; uint8 DefaultTOS; uint8 DefaultTTL; boolean DHCPEnabled; datetime DHCPLeaseExpires; datetime DHCPLeaseObtained; string DHCPServer; string DNSDomain; string DNSDomainSuffixSearchOrder[]; boolean DNSEnabledForWINSResolution; string DNSHostName; string DNSServerSearchOrder[]; boolean DomainDNSRegistrationEnabled; uint32 ForwardBufferMemory; boolean FullDNSRegistrationEnabled; uint16 GatewayCostMetric[]; uint8 IGMPLevel; uint32 Index; uint32 InterfaceIndex; string IPAddress[]; uint32 IPConnectionMetric; boolean IPEnabled; boolean IPFilterSecurityEnabled; boolean IPPortSecurityEnabled; string IPSecPermitIPProtocols[]; string IPSecPermitTCPPorts[]; string IPSecPermitUDPPorts[]; string IPSubnet[]; boolean IPUseZeroBroadcast; string IPXAddress; boolean IPXEnabled; uint32 IPXFrameType[]; uint32 IPXMediaType; string IPXNetworkNumber[]; string IPXVirtualNetNumber; uint32 KeepAliveInterval; uint32 KeepAliveTime; string MACAddress; uint32 MTU; uint32 NumForwardPackets; boolean PMTUBHDetectEnabled; boolean PMTUDiscoveryEnabled; string ServiceName; uint32 TcpipNetbiosOptions; uint32 TcpMaxConnectRetransmissions; uint32 TcpMaxDataRetransmissions; uint32 TcpNumConnections; boolean TcpUseRFC1122UrgentPointer; uint16 TcpWindowSize; boolean WINSEnableLMHostsLookup; string WINSHostLookupFile; string WINSPrimaryServer; string WINSScopeID; string WINSSecondaryServer;
Since the scripts are querying for information it is best if it runs from a DC or a privileged server with an account that has privileged access.
To get the results you need the following two scripts:
I needed to get all the network information for all the domain controllers in the domain. So the following code retrieves it for me. This came really handy in viewing all the DNS settings setup on all the DCs and correcting them if needed.
This will get the information and export to an excel file that you can have handy for reference or auditing. Hope this helps!
]]>To find out which objects were not getting the inherited permissions run the following :
I ran it on the entire domain to identity potential problem accounts. 
To fix the issue:
Reference:
https://docs.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.objectsecurity.areaccessrulesprotected?view=netframework-4.8
https://blogs.msdn.microsoft.com/adpowershell/2009/10/22/viewconfigure-protected-acl-and-fixing-broken-inheritance/
Wrote a small little script to provide me all the information I needed:
Import-Module ActiveDirectory
function Get-AllDCsInForest{
[CmdletBinding()]
param(
[string]$ReferenceDomain = $env:USERDOMAIN
)
$ForestObj = Get-ADForest -Server $ReferenceDomain
foreach($Domain in $ForestObj.Domains) {
Get-ADDomainController -Filter * -Server $Domain | select Domain,HostName,Site, IPv4Address, OperatingSystem, OperatingSystemVersion
}
}
Get-AllDCsInForest| Export-Csv -Path C:\Scripts\AllDcs.txt -NoTypeInformation
]]>
Here is a quick way to do it via PowerShell.
Hope this helps!
]]>Log on to one of your Domain Controllers. Start Windows PowerShell with administrative privileges. Using the Get-ADDomain cmdlet we can get the domain name and the domain partition.
Now depending on the size of your environment the results may take some time to appear. The final output should be something like this:

Now if your application server has multiple drives and depending on which drive the application resides it may be hard to find this information.
Let’s assume there are two drives C: and D:.
We will start with D: drive as it is easy.
#Check DotNet Framework for .EXE & .DLL #====================================# #====================================# #Files residing on any other drive except C: (OS Drive) #=====================================================# #Uncomment the line below to surpress any errors #$ErrorActionPreference = "SilentlyContinue" #Specifiy the filepath (I am using the root) $filepath=â€D:\â€
#Get All files and filter .exe and .dll files $files=Get-ChildItem -Path $filepath -Recurse -include *.dll,*.exe #Loop through each file foreach($file in $files) { #Check the version of .NET for the file $version = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($file.FullName).ImageRuntimeVersion; #Write the Output on Screen + Capture to a file Write-Output "$file,$version" | Out-File D:\DotNetFiles_D.txt -Append }
Now the C: drive is a little more work. The above method wont work because C:Â drive has system files and depending on your rights you may not have access to them.
You may get the following error:

But there is a way we can get this accomplished. Good old dos commands to the rescue! We are basically going to get a list of .exe and .dll files from the C: drive and then run the above code against it.
Lets capture the files:
#For files residing on C: (OS Drive) #====================================# #Get a list of .exe files on the C: Drive and store to a file dir C:\*.exe /s /b | findstr /e .exe > C_Executable_Paths.txt #Get a list of .dll files on the C: Drive and store to a file dir C:\*.dll /s /b | findstr /e .dll > C_DLL_Paths.txt
Now we have the .EXE files stored in C_EXE_Paths.txt and we query it for .NET versions and save the output to DotNetFiles_C_EXE.txt
#Query each .EXE file capture in C_Executable_Paths.txt
$files=Get-Content D:\C_Executable_Paths.txt
#Looping through each file entry
foreach($file in $files)
{
#Getting .NET version number for each file
$version = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($file).ImageRuntimeVersion;
#Writing output to an external file
Write-Output "$file,$version" | Out-File D:\DotNetFiles_C_EXE.txt -Append
}
Similarly we have the .DLLfiles stored in C_DLL_Paths.txt and we query it for .NET versions and save the output to DotNetFiles_C_DLL.txt
#Query each .DLL file capture in C_DLL_Paths.txt
$files=Get-Content D:\C_DLL_Paths.txt
#Looping through each file entry
foreach($file in $files)
{
#Getting .NET version number for each file
$version = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($file).ImageRuntimeVersion;
#Writing output to an external file
Write-Output "$file,$version" | Out-File D:\DotNetFiles_C_DLL.txt -Append
}
You might get errors for files that do not meet criteria or fails to list .Net version.

This can be surpressed by using:
$ErrorActionPreference = "SilentlyContinue"
The output would be similar to:
C:\Program Files\IBM\SQLLIB\BIN\db2dascmn.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2dascmn64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2daskrb.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2daskrb64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2daswrap.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2daswrap64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2g11n.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2g11n64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2genreg.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2genreg64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2hrec.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2ica.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2ica64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2install.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2install64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2isys.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2jcct2.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2jdbc.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2jdbc64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2kbc.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2kbc64.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2ldap.dll,v4.0.30319 C:\Program Files\IBM\SQLLIB\BIN\db2ldap64.dll,v4.0.30319
Now you can import this in Excel and go crazy! 
Additionally, if you want to detect what version of .NETis installed on your server here is a cool utility (ASoft .NET Version Detector) to get you the info, as well as download links to the installer in case you need to download and install.

To begin with, letâ€
s assume a couple things.
Now that weâ€
ve established a baseline, there are a couple of options to perform the task of provisioning an AD user, creating a mailbox, and assigning an Office 365 license.
In this post, I will cover the second option simply because it includes fewer steps and attempts to avoid confusion around where the mailbox should be created.
[su_note note_color=”#ee899a”]Do not create an AD user and then go to the Office 365 portal to create a new user and associated mailbox. This method will not properly create a synchronized O365 user and mailbox.[/su_note]
From the Exchange server, first create the AD user with remote mailbox using one command via Exchange Management Shell (EMS or Exchange PowerShell)…
New-RemoteMailbox -UserPrincipalName "[email protected]" -Alias "UserTest" -Name "UserTest" -FirstName "User" -LastName "Test" -DisplayName "User Test" -OnPremisesOrganizationalUnit "Office 365 Users" -Password (ConvertTo-SecureString "EnterPasswordHere" -AsPlainText -Force) -ResetPasswordOnNextLogon $true
In the command above, I created the AD user in an OU named “Office 365 Usersâ€, set the password to “EnterPasswordHereâ€, and will require the user to change their password at next logon. However, I did not assign an SMTP address or remote routing address assuming that the email address policies are configured to be applied as new mailboxes are created.
Once the AD user and mailbox are created, the AD object must to be synchronized to O365 in order to add the user with associated mailbox in the tenant. With the new version of AAD Connect, the scheduled sync time occurs every 30 minutes. In my case, Iâ€
m not that patient and will manually force a sync to O365.
From the server with AAD Connect installed, via an elevated PowerShell console, run the following command to perform the sync to O365…
Start-ADSyncSyncCycle -PolicyType Delta
This task will synchronize all changes made to AD since the user and mailbox were created.
In the final step, I assign an O365 license to the newly created and synchronized user. The following commands can be run from any machine that has both Microsoft Online Services Sign-in Assistant for IT Professionals RTW and Windows Azure Active Directory Module for Windows PowerShell installed. In my case, they are installed on each server, as well as my admin workstation.
Connect to O365 via PowerShell from an elevated PowerShell console; or using Azure AD Module for PowerShell console.
Confirm the new user does not have an O365 license assigned.
#Connect to Office365 Import-Module MSOnline Connect-MsolService $O365Cred = Get-Credential $O365Session = New-PSSession –ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $O365Cred -Authentication Basic -AllowRedirection Import-PSSession $O365Session Get-MsolUser -UnlicensedUsersOnly
This command returns unlicensed O365 users in which the “isLicensed†parameter is “Falseâ€.
The next command returns the “AccountSkuId“, or subscription license(s), of my tenant that I will use to assign to the new user.
Get-MsolAccountSku
The AccountSkuId will look something similar to “tenantname:ENTERPRISEPACK“; where “ENTERPRISEPACK†represents my Office 365 Enterprise E3 subscription. Other subscriptions will have different representations.
Before I can assign any licenses to my new user, the user must be assigned a location (or country code). Since Iâ€
m am located in the United States, I use “US†as the two letter country code for the user, using this command…
Set-MsolUser -UserPrincipalName [email protected] -UsageLocation US
Now that Iâ€
ve set a location for the new user, I can assign a license from my associated O365 subscription, using this command…
Set-MsolUserLicense -UserPrincipalName [email protected] -AddLicenses tenantname:ENTERPRISEPACK
Finally, the user can access their assigned mailbox in Exchange Online.
]]>