Samba share with AD auth, 2020 May edition


I wrote about this topic almost 4 years ago: Samba share with AD authentication
This article is the updated version. It has a different environment and purpose, as well as a new version of samba that requires a workaround.
The goal today is just get a quick home directories share.


  • Server is joined to the domain
  • Working on CentOS 7. The previous article included Ubuntu commands for the package manager and firewall.

Setting up Samba

Install the packages, including the server package.

yum -y install samba

Open the firewall.

firewall-cmd --permanent --add-service=samba
systemctl restart firewalld.service

Configure Samba.

cat <<EOFSMB > /etc/samba/smb.conf
   workgroup = EXAMPLE
   security = ads
   realm = EXAMPLE.COM
   kerberos method = system keytab
   netbios name = $( hostname -s )
   server string = Description here
   log file = /var/log/samba/log.%m
   max log size = 50
   dns proxy = no
   encrypt passwords = yes
   passdb backend = tdbsam
   printcap name = /dev/null
   load printers = no

   comment = Home Directories
   valid users = user1, user2, @group1
   browseable = No
   read only = No
   inherit acls = Yes
   guest only = no

Starting with Samba 4.9.1, a workaround is needed for Samba to work when the id mapping is not set up thoroughly. This example does not do any id mapping, so use this quick and dirty fix.

net -s /dev/null groupmap add sid=S-1-5-32-546 unixgroup=nobody type=builtin

You can see the custom mapping for the guest user with:

$ net -s /dev/null groupmap list
nobody (S-1-5-32-546) -> nobody
Reference: 1648399 – Samba 4.9.1: smb.service fails with ERROR: failed to setup guest info (RHBZ)

And enable and start the services.

systemctl enable --now smb nmb

This command enables (sets to run at system startup) and starts immediately, these two services. NMB is the NetBIOS name server. It helps the main Samba daemon in ways deeper than I care to research.

Configuring SELinux

Set a few SE booleans.

for word in samba_export_all_rw samba_create_home_dirs ; do setsebool -P "${word}" 1 ; done

Use Images from Google Photos album as screensaver on Linux


I wanted to set up a (shared) Google Photos album as my screensaver on Devuan GNU/Linux. I accomplished this with the venerable xscreensaver and a useful tool named rclone which I was already using.

Downloading images from Google Photos

On my CentOS 7 server, I already am using rclone. Starting with version 1.49, rclone supports Google Photos. EPEL provides rclone but only version 1.47. So download the upstream rpm release: Downloads page or direct link.
The utility comes with a cli-based configurator. Use rclone config and add a remote. I failed to save the output of when I configured it, but it’s as easy following the prompts to add a Google Photos remote. Choose the headless option, and it will show you a link to open in a browser where you can authenticate to Google Photos and then approve this application to use a token tied to your account. The browser will show you a code you paste back into the configurator.
In the end, what matters is that the ~/.config/rclone/rclone.conf includes a section like so:

type = google photos
read_only = true
token = {"access_token":"za29.a0AfH6SMC9bVDgJTPZTPfvMd5MyplOehAgldvibfrlmMwOBFUSCATEDuDd6tMuWm13YOryXXpUWP4EkHO-oNoa92I6WN7rzrS4y6hquw3zPFXOayEZv-3uv9_NfYNUyctoBq_OUQ9lrqoH3U7J40crJW2NZ","token_type":"Bearer","refresh_token":"1//013Vdw5CGVJ3zCgZIARAAGAFSNwF-L9IricqZmAio2CqpFJ8nM0OuXmsorNlVHRmX5w_KeIgvwvrxsJl5hJT30jT9ET-9mh612-0","expiry":"2020-05-19T10:49:30.709779985-04:00"}

And then my sync script is this:

# File: /etc/installed/
# Location: storage1:/etc/installed
# Author:
# Startdate: 2020-05-19
# Title: Script that Pulls Down Album from Google Drive
# Purpose: Pull photos down for a screensaver for the living room HTPC
# History:
# Usage:
#    in a cron entry:
# References:
#    man rclone(1)
# Dependencies:
#    ~/.config/rclone/rclone.conf with a section named "photos"
# Documentation:
#    --dry-run is useful. Sync command runs from left side to right side only.
INDIR="shared-album/Album Name"
LOGFILE=/mnt/public/Support/Systems/storage1/var/log/sync-photos/$( date "+%F" ).log
mkdir -p "$( dirname "${LOGFILE}" )"
PLECHO="$( which plecho 2>&1 || which cat )"
time rclone -v --gphotos-read-only sync "${REMOTE}:${INDIR}" "${OUTDIR}" 2>&1 | "${PLECHO}" | tee -a "${LOGFILE}"

The script itself ran great the first time, and downloaded all images (and videos) from the album. Upon second run though, the task seems stuck. Ah, well, I’ll solve that over time.
For a cron job to run this daily, add file /etc/cron.d/90_google-photos-sync.cron

# File: /etc/cron.d/90_google-photos-sync.cron
# for google photos album shared by another google user. Save down all images so that HTPC can use them in the screensaver.
45 04 *  *  *  root  /etc/installed/ 1>/dev/null 2>&1

And as with all good cron jobs, suppress the standard output and standard error and use the job itself to store logs.

Using images in xscreensaver

On my Devuan GNU+Linux home theater PC, I use xscreensaver. To take advantage of a simple slideshow feature, make sure to have package xscreensaver-gl installed.
Xscreensaver settings on main tab showing GLSlideshow selected
Select GLSlideshow and option “Only one screen saver.” The Tesselimage screensaver was very entertaining when used on personal photos but not all end users agreed with me.
Xscreensaver GLSlideshow settings page showing sliders with useful settings
Adjust sliders as necessary. I found that on the default settings, the slideshow would fade out and back in, on the same photo, about three times, before finally moving on to the next image. So these sliders’ positions were successful in always showing a new image on each fadeout, which was also slowed down.
On the advanced settings tab of GLSlideshow, the sliders were instantiated as:

glslideshow -root -delay 30081 -duration 10 -pan 10

Xcreensaver settings on the advanced tab showing what directory to pull random images from
And of course, choose the destination directory (nfs mounted in my case) on the advanced settings.

For the really hardcore users, some of the relevant .xscreensaver values I could find include:

chooseRandomImages: True
imageDirectory:   /mnt/public/Images/google-drive/photos
mode:    one
selected:       143
programs:                              \
- GL:             glslideshow -root -delay 30081 -duration      \
              10 -pan 10                \n\

It is worth noting that the glslideshow entry is 143 lines below the programs: line.

Extra thoughts

Even though a Google Photos album that is shared might be accessed anonymously, the rclone utility requires an account.
Linux – keep it simple.



Update osc password for Open Build Service

After the Open Build Service’s effort to migrate to the new authentication system, I went ahead and just rotated my password.

adrianSuSE wrote 2 days ago: OBS has switched to the new authentification system:

Of course my osc commands started failing on the command line.

The man page for osc is a little obtuse. You have to omit the word “section” when changing the password.

osc config --change-password

And then osc will be back on track!

Share your browser prefs.js!

The advances users of Mozilla-based web browsers tend to have a large set of preferences. Let’s start a trend of sharing them!

I throw all my settings into one prefs.js that gets distributed through my various means to the system directory. That’s why you see pref() here instead of user_pref(). Not all of these options apply to each browser, but the extra ones do not hurt. So these could affect Waterfox, Palemoon, or Firefox web browsers.

// file: /usr/lib/waterfox/browser/defaults/preferences/bgstack15-waterfox-prefs.js
// last modified 2020-04-15
// reference:
// Turn off updates. I use my package manager for browser updates.
pref("",                     false);
pref("app.update.autoInstallEnabled",       false);
pref("app.update.enabled",                  false);
pref("extensions.update.autoUpdateDefault", false);
// Disable previews of tabs. I just do not like the feature
pref("browser.allTabs.previews", false);
pref("browser.ctrlTab.previews", false);
pref("browser.ctrlTab.recentlyUsedOrder", false);
// Old-style backspace action to navigate backwards through browsing history.
pref("browser.backspace_action", 0);
// Do not prompt for download location. Just use ~/Downloads.
pref("", true);
// Show blank page on a new tab.
pref("browser.newtab.choice", 1);
pref("browser.newtabpage.enabled", false);
pref("browser.newtabpage.storageVersion", 1);
// Do not suggest similar searches when typing into the bar. 
pref("", false);
// Disable these by choice.
pref("browser.safebrowsing.malware.enabled", false);
pref("browser.safebrowsing.phishing.enabled", false);
// Hide this search plugin.
pref("", "DuckDuckGo");
// Do not automatically check for updates to search plugins.
pref("", false);
// Do not show separate widget in navigation bar.
pref("", false);
// Load all tabs when starting browser. I hate load-on-demand, which is when it loads only when you switch to that tab.
pref("browser.sessionstore.restore_on_demand", false);
// Do not check if this is the OS default browser.
pref("",   false);
// Set my home page.
pref("browser.startup.homepage",            "data:text/plain,browser.startup.homepage=");
pref("", 3);
// Closing the last tab does not close the browser window.
pref("browser.tabs.closeWindowWithLastTab", false);
// Obviously I did not type this one myself. This controls the layout of the buttons on the navigation bar.
pref("browser.uiCustomization.state", "{\"placements\":{\"PanelUI-contents\":[\"edit-controls\",\"zoom-controls\",\"new-window-button\",\"e10s-button\",\"privatebrowsing-button\",\"save-page-button\",\"print-button\",\"history-panelmenu\",\"fullscreen-button\",\"find-button\",\"preferences-button\",\"add-ons-button\",\"developer-button\",\"sync-button\"],\"addon-bar\":[\"addonbar-closebutton\",\"status-bar\"],\"PersonalToolbar\":[\"personal-bookmarks\"],\"nav-bar\":[\"urlbar-container\",\"bookmarks-menu-button\",\"downloads-button\",\"home-button\",\"jid1-n8wh2cbfc2qauj_jetpack-browser-action\",\"ublock0_raymondhill_net-browser-action\",\"_f73df109-8fb4-453e-8373-f59e61ca4da3_-browser-action\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"toolbar-menubar\":[\"menubar-items\"]},\"seen\":[\"jid1-n8wh2cbfc2qauj_jetpack-browser-action\",\"ublock0_raymondhill_net-browser-action\",\"_f73df109-8fb4-453e-8373-f59e61ca4da3_-browser-action\",\"developer-button\"],\"dirtyAreaCache\":[\"PersonalToolbar\",\"nav-bar\",\"TabsToolbar\",\"toolbar-menubar\",\"PanelUI-contents\",\"addon-bar\"],\"currentVersion\":6,\"newElementCount\":0}");
// Use dense view.
pref("browser.uidensity", 1);
// Do not hide the http and colon-slash-slash elements of a URL in the url bar.
pref("browser.urlbar.trimURLs", false);
// Disable enlarged-upon-selected url bar.
pref("browser.urlbar.update1", false);
// Allow me to see ssl error messages (and the ability to continue past them).
pref("browser.xul.error_pages.enabled", false);
// Do not use webcam for this feature.
pref("camera.control.face_detection.enabled", false);
// Null-route these URLs
pref("captivedetect.canonicalURL", "");
pref("", "");
pref("dom.push.serverURL", "wss://");
pref("security.ssl.errorReporting.url", "");
pref("services.settings.server", "");
pref("", "");
// Metadata that is not very important but it ended up in my copy-paste work.
pref("distribution.stackrpms.bookmarksProcessed", true);
// Disable Mozilla experiments.
pref("experiments.activeExperiment", false);
// This plugin is probably uBlock origin.
pref("extensions.enabledAddons", "%7B972ce4c6-7e08-4474-a285-3208198ce6fd%7D:28.3.0");
// Tell the browser that we already showed the user the page for "Select your addons" so it does not bother the user.
pref("extensions.shownSelectionUI", true);
// Already assume these plugins are enabled. This can help suppress the warning, "An admin added these plugins. Please choose to enable them or not."
pref("extensions.webextensions.uuids", "{\"\":\"7f64930e-0e43-4813-97c3-6fcb8a82e63b\",\"jid1-n8wH2cBfc2QaUj@jetpack\":\"5b1c5018-34cd-4778-902b-08741e3d0002\",\"{f73df109-8fb4-453e-8373-f59e61ca4da3}\":\"b7ece467-f6eb-4254-a815-1029330a9793\"}");
// Select "Highlight all" for the find function.
pref("findbar.highlightAll", true);
// Miscellaneous
pref("gecko.handlerService.migrated", true);
pref("marionette.prefs.recommended", false);
pref("network.cookie.prefsMigrated", true);
pref("privacy.sanitize.migrateFx3Prefs", true);
// Do not warn me when entering about:config
pref("general.warnOnAboutConfig", false);
// Disable geolocation functions
pref("geo.enabled", false);
// Trust my domain for Kerberos authentication
pref("network.automatic-ntlm-auth.trusted-uris", "");
pref("network.negotiate-auth.trusted-uris", "");
// Disable the captive portal detection logic.
pref("network.captive-portal-service.enabled", false);
// Disable dns prefetching (exactly what it sounds like).
pref("network.dns.disablePrefetch", true);
// Disable whatever these are.
pref("network.predictor.enabled", false);
pref("network.prefetch-next", false);
// Disable requiring HSTS. Use at my own risk!
pref("network.stricttransportsecurity.preloadlist", false);
// Disable Reader mode.
pref("reader.parse-on-load.enabled", false);
// More personal ssl choices. Use at my own risk!
pref("security.cert_pinning.enforcement_level", 0);
// Hide these search plugins. Somehow the Debian package search keeps getting re-enabled so I need to work on this one.
pref("services.sync.declinedEngines", "");
// Startup home page (if I were to choose the option "Load these pages at startup," which I did not)
pref("startup.homepage_override_url",       "");
pref("startup.homepage_override_url", "");
// Hm, there should be more of these, particularly toolkit.telemetry.enabled = false
pref("toolkit.telemetry.reportingpolicy.firstRun", false);
// Trust these domain names for installing extensions: none!
pref("xpinstall.whitelist.add", "");
// Control DNS over HTTPS (DoH) and Trusted Recursive Resolver (TRR).
// More about DoH:
// 0: Off by default, 1: Firefox chooses faster, 2: TRR default w/DNS fallback,
// 3: TRR only mode, 4: Use DNS and shadow TRR for timings, 5: Disabled.
pref("network.trr.mode", 0);
// Disable Pocket and null-route the URLs.
pref("extensions.pocket.enabled", false);
pref("extensions.pocket.api", "http://localhost:9980");
pref("", "http://localhost:9980");

Hide debian-style motd

If you use Debian-family systems in a networked environment and log in through ssh, you might see this message a lot:

Linux d2-03a 5.5.0-2-amd64 #1 SMP Debian 5.5.17-1 (2020-04-15) x86_64

The programs included with the Devuan GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Devuan GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed May  6 19:12:04 2020 from

I finally decided to do something about this message that I see every time.
Find any lines in /etc/pam.d/sshd that are uncommented and that call, and comment the line out.

sudo sed -i -r -e '/{/^#/!{s/^/#/;};}' /etc/pam.d/sshd

turn dnf color off

Sometimes, color on the console is a good thing. Sometimes, it’s distracting.

I upgraded to Fedora 31, and was surprised to see colorized output for dnf, the web-aware package manager that wraps around rpm.

I was not in a mood to deal with different colors of package names. Yum/dnf already has great output (and apt does not) and needs no further improvement.

So to turn off the dnf color output, just set “color=never” in the “[main]” section of dnf.conf. For users of my bgscripts package from my stackrpms copr, use modconf.

sudo /usr/libexec/bgscripts/py/ -a /etc/dnf/dnf.conf -s 'main' set color never

txt2man wrapper which adds man page headings support in text file

txt2man is great. It depends on only GNU awk, which I can certainly live with. I previously used go-md2man which is also fine, but I just don’t want to depend on golang.

txt2man has an area where I wanted to improve it, but I didn’t know how to contribute to upstream in a way that I expect they would want. So I wrote txt2man-wrapper! Txt2man adds the page titles, section number, etc., from parameters on the command line invocation, but I wanted to add the ability to store these values in the text file.

# File: txt2man-wrapper
# Location: /usr/bin
# License: CC-BY-SA 4.0
# Author: bgstack15
# Startdate: 2020-04-15 17:35
# Title: Txt2man wrapper that adds title headings support
# Purpose: add title headings support to txt2man
# History:
# Usage:
# Custom layout for input text file, where first lines include "section 3", "title txt2man-wrapper", "project bgscripts", "volume Linux command reference" in no particular order, followed by "====="
# Reference:
# txt2man
# Improve:
# Dependencies:
# dep-raw: /usr/bin/awk, /usr/bin/txt2man
# rec-devuan: gawk | mawk, txt2man
# rec-fedora: coreutils, txt2man
test -z "${INFILE}" && test -n "${1}" && export INFILE="${1}"
if test -z "${INFILE}" || test "${INFILE}" = "-" ; then USE_STDIN=1 ; fi

if test "${USE_STDIN}" = "1" ;
input="$( cat )"
input="$( cat "${INFILE}" )"

head="$( echo "${input}" | awk 'BEGIN{a=0} /^=====/{a=1} {if(a==0)print}' )"
short="$( echo "${input}" | awk '{if(a)print} /^=====/{a=1}' )"

ul="$( echo "${head}" | awk '$1=="title" {$1="";print}' | sed -r -e 's/^\s*//;' )"
uln="$( echo "${head}" | awk '$1=="section" {$1="";print}' | sed -r -e 's/^\s*//;' )"
dc="$( echo "${head}" | awk '$1=="date" {$1="";print}' | sed -r -e 's/^\s*//;' )"
ur="$( echo "${head}" | awk '$1=="project" {$1="";print}' | sed -r -e 's/^\s*//;' )"
uc="$( echo "${head}" | awk '$1=="volume" {$1="";print}' | sed -r -e 's/^\s*//;' )"
bolds1="$( echo '`__xxNONExx__`' "${short}" | tr -d '\r\n' | tr '`' '\n' | awk 'NR/2==int(NR/2){print}' | xargs -n1 printf '%s %s ' '-B' )"
bolds2="$( echo "*__xxNANExx__*" "${short}" | tr -d '\r\n' | tr '*' '\n' | awk 'NR/2==int(NR/2){print}' | xargs -n1 printf '%s %s ' '-B' )"
ital1="$( echo "${short}" "**__xxNBNExx__**" | tr -d '\r\n' | sed -r -e 's/\*\*/\n/g;' | awk 'NR/2==int(NR/2){print}' | xargs -n1 printf '%s %s ' '-I' )"
test -n "${DEBUG}" && echo "${bolds1}" "${bolds2}" "${ital1}" 1>&2
echo "${short}" | txt2man -t "${ul}" -r "${ur}" -s "${uln}" -v "${uc}" -d "${dc}" ${bolds1} ${bolds2} ${ital1}

Using my handy txt2man-wrapper above, or found in my bgscripts package, you can have a text file suitable for txt2man, with some extra header lines at the top.

title sizer
section 1
project bgscripts-core
volume General Commands Manual
date April 2020
  sizer - summarize directory contents by file extension

And invoke txt2man-wrapper like so:

txt2man-wrapper < sizer.1.txt | gzip > sizer.1.gz

In a Makefile, you can use something similar to:

MAN_TXT:=$(wildcard usr/share/man/man*/*.txt)
MAN_GZ:= $(subst .txt,.gz,$(MAN_TXT))

build_man: $(MAN_GZ)

$(MAN_GZ): %.gz: %.txt
   txt2man-wrapper - < $< | ${gzipbin} > $@

Deplist improved, with suggested and recommended entries

make sure to document how to use `make DEPLIST DEPTYPE=sug DISTRO=devuan` and default of `make DEPLIST DEPTYPE=dep DISTRO=devuan` and make DEPLIST DEPTYPE=rec DISTRO=devuan

Here is my improved method for using target deplist.

In the Makefile:

   @# deplist 2020-04-18 input must be comma separated
   @# DEPTYPE( dep , rec , sug ) for depends, recommends, or suggests
   @if test -z "${DISTRO}" ; then ${echobin} "Please run \`make deplist\` with DISTRO= one of: `make deplist_opts 2>&1 1>/dev/null | ${xargsbin}`. Aborted." 1>&2 ; exit 1 ; fi
   @if ! ${echobin} "${DEPTYPE}" | grep -qE "^(dep|rec|sug)$$" ; then ${echobin} "Please run \`make deplist\` with DEPTYPE= one of: dep, rec, sug. Undefined will use \`dep\`. Aborted." 1>&2 ; exit 1; fi
   @${grepbin} -h --exclude-dir='doc' -riIE "\<${DEPTYPE}-" ${SRCDIR} | ${awkbin} -v "domain=${DISTRO}" -v "deptype=${DEPTYPE}" 'tolower($$2) ~ deptype"-"domain {$$1="";$$2="";print}' | tr ',' '\n' | ${sortbin} | ${uniqbin} | ${sedbin} -r -e 's/^\s*//' -e "s/\s*\$$/${SEPARATOR}/" | ${xargsbin}

   @# deplist_opts 2020-04-18 find all available dependency domains
   @${grepbin} -h -o -riIE '\<(dep|rec|sug)-[^\ :]+:' ${SRCDIR} | ${sedbin} -r -e 's/(dep|rec|sug)-//;' -e 's/:$$//;' | ${sortbin} | ${uniqbin} 1>&2

Observe how these all depend on variables being defined earlier:

awkbin     :=$(shell which awk)
chmodbin   :=$(shell which chmod)
cpbin      :=$(shell which cp)
echobin    :=$(shell which echo)
falsebin   :=$(shell which false)
findbin    :=$(shell which find)
grepbin    :=$(shell which grep)
gzipbin    :=$(shell which gzip)
installbin :=$(shell which install)
rmbin      :=$(shell which rm)
rmdirbin   :=$(shell which rmdir)
sedbin     :=$(shell which sed)
sortbin    :=$(shell which sort)
truebin    :=$(shell which true)
uniqbin    :=$(shell which uniq)
xargsbin   :=$(shell which xargs)

In the source files in the project, you can use entries like these:

# dep-devuan: mawk | gawk, lightdm, upower
# dep-raw: awk, grep, sed, lightdm
# rec-devuan: lightdm-gtk-greeter
# sug-devuan: logout-manager
# dep-centos: lightdm, upower

Obviously this is a poor substitute for the make_shlibs from debhelper (which probably just wraps around ldd). But for my shell-driven packages, it’s pretty nice if you add the dependencies to each individual file.

Use login form for Jira even when SAML auth is primary

If you use SAML authentication as primary auth for Jira, but it malfunctions, you can still get in with a local account.

Use this URL: The auth_fallback parameter will skip the redirection for unauthenticated users.

How to enable this feature

You have to tell Jira to listen for this parameter. Hit this REST endpoint:

curl -vvv -X PUT -H 'Content-Type: application/json' -d '{"allow-redirect-override": true}' -u 'internalapplications'

You can also append a colon and the password in the -u parameter.

Bonus content

Use the Announcement banner for a “Login with SSO” button, and even a redirection to the login form, if the user visits anything other than the login page while unauthenticated.

if(jQuery('#header-details-user-fullname').text().indexOf(" ")==-1 && window.location.href.indexOf("login.jsp")==-1)
{ window.location = "/login.jsp" }

if(window.location.href.indexOf("login.jsp") != -1) {
    jQuery( "<a class='aui-button aui-button-primary' href='/plugins/servlet/external-login' id='SSOLogin' target='_parent' resolved=''>Use SSO Login</a>" ).insertAfter( '#login-form-submit' );

jira login form for manual entry, with additional "Use SSO Login" button



  1. Bypass SAML authentication for Jira Data Center – Atlassian Documentation refers to a command that is not valid for Jira Software 8.7.1.
    curl -vvv -XPUT /rest/authconfig/1.0/saml -H 'Content-Type: application/json' -d '{"allow-saml-redirect-override": true}' -u admin_username


Original research with REST API browser

Powershell: get list of users and their groups

The Powershell objects for AD users contain a calculated attribute, MemberOf. If you want to merely get the names of said groups, and not the DNs, as an attribute of the users, you will have to transform the objects with more than a one-liner.

$results = Get-ADUser -SearchBase 'OU=Users,DC=example,DC=com' -LDAPFilter "(EmployeeID=*)" -Properties EmployeeID, GivenName, Surname, EmailAddress, Manager, Department, physicalDeliveryOfficeName, Title, Mobile, MemberOf, UserPrincipalName, PrimaryGroup, SamAccountName, displayName, country, departmentNumber, adminDisplayName, LockedOut, Enabled | ? { $_.enabled -eq $True }
$x = 0
$xtotal = $results.count
$out = ForEach ($tu in $results) {
   $x = $x + 1
   if ( ($x/50) -eq [int]($x/50) ) { Write-Progress -Activity "Enumerating groups for each user" -PercentComplete ($x/$xtotal*100) }
   $groups = ForEach($tg in $tu.MemberOf) { (Get-ADGroup $tg).Name } ;
   $groupStr = $groups -join ":" ;
   $tu | Select-Object EmployeeID, GivenName, Surname, EmailAddress, Manager, Department, physicalDeliveryOfficeName, Title, Mobile, UserPrincipalName, PrimaryGroup, SamAccountName, displayName, country, departmentNumber, adminDisplayName, LockedOut, Enabled,@{n='Groups';e={$groupStr} ;
   } ;

Which you can then export to a csv file.

$out | Export-Csv Users.csv


Go ahead, and tell me that I can include the enabled filter in the -LdapFilter parameter. I didn’t feel like looking up the complicated bitmask comparison required for that. Microsoft stores Enabled=True in ldap in a complex object attribute.



  1. Ripped mostly from [SOLVED] Format memberof attribute – PowerShell – Spiceworks
  2. write-progress syntax Using Write-Progress to provide feedback in Powershell