Adding an Application Registration\ Service Principal to another Application Registration\ Service Principal

Typically when working with App Roles in Azure Active Directory for a single application registration or service principal and then self consuming that app role as an Application API Permission you would see in the Enterprise Application > Users and Groups blade that service principals are added.

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:

Get Inactive Users Report for the past 60 days in a multi domain environment

I had a request recently to provide an inactive user report for the past 60 days. Basically, find out which accounts have not logged in for the past 60 days so action can be taken against them.

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.

Get Primary, Secondary, Tertiary DNS values and more from Multiple Servers

Came across a unique request to get primary, secondary, and tertiary DNS values for multiple computers/servers across the domain. I started writing the script and got what I wanted.

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!

Fix Active Directory broken security inheritance problem

Ran into a situation at a client location where in Active Directory, the security permissions applied to an OU were not getting inherited permissions on to the objects. Basically, security inheritance was broken.This causes a problem when the administrative accounts or groups needing to modify an attribute on the AD object throw errors, or are unable to edit the AD object.

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/

Get All DCs in the Entire Forest

Getting a know a new environment for a new client and I a quickly needed information about all domain controllers in the entire forest.

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

 

Get .Net Framework Version for the .DLL & .EXE files

Working with many app/dev teams it is hard to find which version of Dot Net  an application was designed or made in.

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.

Provisioning a New Office 365 User and Mailbox from Exchange Hybrid via PowerShell

Working with many Office365 clients, I receive queries on how to go about provisioning users and mailboxes for an Exchange hybrid deployment.

To begin with, let’s assume a couple things.

  1. We have a Windows 2012 R2 member server with Azure AD Connect (AAD Connect) version 1.1.105.00 (or newer) and the Azure AD Module for PowerShell installed; and
  2. We have an Exchange 2013 CU11 (or newer) server configured for hybrid with an active O365 tenant.

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.

  1. The first option would be to create an AD user, create an on premise mailbox, migrate the mailbox to Office 365, and assign a license; or
  2. The second option would be to create an AD user, create a remote (or Office 365) mailbox, and assign a 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]

STEP 1: CREATE USER & MAILBOX

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.

STEP 2: SYNCHRONIZE USER

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.

STEP 3: ASSIGN LICENSE

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.