1

Тема: WSH: блокировка учетных записей, неактивных более 30 дней

Добрый день! Извиняюсь за путанность вопроса, но мне очень нужна ваша помощь, я системный администратор в фирме, у меня сейчас появился новый заказчик котрый проверяет систему безопасности на соответствие стандартам и выяснилось что тнеобходима один скрипт, может кто-то уже сталкивался с этим делом. задача вроде тривиальная, но у меня решить ее не получилось(
Задача:
Мне необходимо написать скрипт блокировки учетных записей неактивных более 30 дней.

Помогите пожалуйста иначе меня съедят((

2

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Извините за неправильный топик, нужен скрипт любого рода для AD windows Server 2003

3

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Для этого вроде бы не нужен скрипт, это можно сделать "вручную". Вроде должен быть способ интерактивно подобрать всех давно не логинившихся, а затем заблокировать их. Пусть проофессионалы меня поправят .

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

4

Re: WSH: блокировка учетных записей, неактивных более 30 дней

К сожалению такого механизма нет, можно каждый раз сидеть и просматривать когда юзер последний раз логинился, но если у вас их 1000. у меня проблема в том, что я не программист, задача для меня  сверхсрочная, общаясь с представителями заказчика выяснилось что единственный вариант это написать скрипт который будет проверять на входе аккаунты по дате входа, а на выходе либо информировать о том что юзер1, 2, 3 превысили максимального время ожидания, либо просто блокировка учетной записи.
К сожалению другого выхода не вижу, в книжке патчи для windows server 2003 описано что стандартными решениями здесь не обойтись. Как вы считаете это сложная задача для написания? Я думаю что этот скрипт облегчит жизнь половине сис.админов всего мира. HELP!

5

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Поло, это штатная задача, хотя и решается только через скрипт и примеров в интернете полно.
если надо могу его прокомментировать на русском, хотя и на англиском вполне понятно что и как.

пример взять с сайта http://www.scriptinganswers.com/vault/A … istration/

   

DIM theDate
DIM UserObj
DIM Object
DIM GroupObj
Dim Flags
Dim Diff
Dim Result
Const UF_ACCOUNTDISABLE = &H0002
' Set this to TRUE to enable Logging only mode - no changes will be made
CONST LogOnly = TRUE
' Point to Object containing users to check
Set GroupObj = GetObject("WinNT://MYDOMAINCONTROLLER/Users")
On error resume next
For each Object in GroupObj.Members
' Find all User Objects Within Domain Users group (ignore machine accounts)
If (Object.Class = "User") and (instr(Object.Name, "$") = 0) then Set UserObj = GetObject(Object.ADsPath)
theDate = UserObj.get("LastLogin")
theDate = Left(theDate,8)
theDate = cdate(theDate)
' find difference in week between then and now
Diff = DateDiff("ww", theDate, Now)
' if 6 weeks or more then disable the account
If Diff >= 6 Then Flags = UserObj.Get("UserFlags")
IF (Flags AND UF_ACCOUNTDISABLE) = 0 Then
' Only disable accounts if LogOnly set to FALSE
If LogOnly = FALSE Then
UserObj.Put "UserFlags", Flags OR UF_ACCOUNTDISABLE
UserObj.SetInfo
End if
strName = UserObj.Name
result = Log(strName,Diff)
End If
end if
end if
Next
Set GroupObj = Nothing

Function Log(User,strDate)
' Constant for Log file path
CONST StrLogFile = "C:\UserMgr1.txt"
Set objFS = CreateObject("Scripting.FileSystemObject")
Set strTextStream = objFS.OpenTextFile(strLogFile, 8, true)
strTextStream.WriteLine("Account:" & vbTab & User & vbTab & "Inactive for:" & vbTab & strdate & vbatb & "Weeks" & vbtab & "Disabled on:" & vbTab & Date & vbTab & "at:" & vbTab & Time)
strTextStream.Close
Set objFS = Nothing
Set strTextStream = Nothing
End Function

6

Re: WSH: блокировка учетных записей, неактивных более 30 дней

1. Установить "Windows Server 2003 Service Pack 2 Administration Tools Pack" (adminpak_sp2.msi), на WinXP SP2 ставится без проблем.
2. Запустить mmc.exe, подключить оснастку "Active Directory Users and Computers".
3. В ветви "Saved Queries" в контекстном меню - "New" - "Query".
4. В окне "New Query" кнопка "Define Query...", затем в окне поиска задать поле "Days since last logon" - 30.
5. В правой панели оснастки выделить всех найденных пользователей, в контекстном меню - "Disable Account".

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

7

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Все спасибо большое. Очень выручили! Если кто-то в Питере, с меня пыво)

8

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Скрипт не пробовал мне он кажеться слишком громоздким для такой простой задачи, у меня по корпоративной политике пользователи не логиневшиеся ~30 дней должны быть перенесенны в OU Inactive и после заблокированны, для это используется:

for /f "Tokens=*" %a in ('dsquery user "dc=domain,dc=com" -scope subtree -inactive 4') do dsmove %a -newparent "ou=inactive,dc=domain,dc=com"

-inactive 4 - пользователи не активные 4 недели

9

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Прошу Разработчиков, кто может, проверить решения в постах #5 и #8, а также прокомментировать #5 по-русски и поместить в Коллекцию.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

10

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Еще один вопрос, оба комментатора пишут срок в неделях, это обязательный момент или возможно в днях, очень долго тестировать на правильность скрипты

11

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Ни один из вышеприведенных способов у меня не заработал, видимо домен поднят не так, как надо.
А вот этот работает. Причем при минимальных правках скрипта срок можно задавать хоть в секундах

var DomainString="kemerovo"
var numDays = 30;

var StdOut = WScript.Stdout;
var USER, diff, LastLogin;
var objDomain = GetObject("WinNT://"+DomainString);

StdOut.WriteLine ("Учетные записи домена "+DomainString+", последний "+
                  "вход под которыми был произведен более " + numDays + " дней назад.\n"+
                  "Begin");

for (var e = new Enumerator(objDomain);  !e.atEnd(); e.moveNext()) {
   USER = e.item();

   if (USER.Class.toUpperCase() == "USER") {

       // Поскольку на некоторых служебных юзерах происходит спотыкач, то предохраняемся
      try {
         LastLogin = new Date (USER.LastLogin);
      }catch(err){
          LastLogin = Date()
          // Можно вывести того, на ком споткнулись
          // StdOut.WriteLine (USER.Name+"\t!!! "+err.message);
      }

      // Вычисляем разницу между текущей датой и датой последнего входа в сутках.
      diff = ((new Date ()).getTime() - LastLogin) / (86400 * 1000);
      if ( diff >= numDays) {
         StdOut.Write (USER.Name);
         StdOut.Write ("\t"+USER.FullName);
         StdOut.Write ("\tПоследний вход: " + LastLogin.toLocaleString());
         StdOut.Write ("\t"+diff.toFixed(0)+" дней");
         StdOut.WriteLine ('');
      }
   }
}

StdOut.WriteLine ("End");

12 (изменено: alexii, 2007-11-22 15:37:24)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

По посту #5: перед тем, как выкладывать, желательно проверить хотя бы синтаксис. Если мы скатимся на метод копи-паст, грош нам цена.
Вывод: в оригинале не работает, после исправления синтаксических ошибок — у меня тоже не работает. Строка

Set GroupObj = GetObject("WinNT://MYDOMAINCONTROLLER/Users")

даёт ошибку

(null): Не найдено имя группы.

По посту #8: то, о чём писал — нет dsquery — опробовать не могу.

По посту #11: отрабатывает нормально.
Вопрос:

delpher пишет:

Поскольку на некоторых служебных юзерах происходит спотыкач, то предохраняемся

У Вас на каких служебных юзерах это происходило? Просто у меня подобного не возникало.

13

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

По посту #5...
Вывод: в оригинале не работает, после исправления синтаксических ошибок — у меня тоже не работает. Строка

Set GroupObj = GetObject("WinNT://MYDOMAINCONTROLLER/Users")

даёт ошибку

(null): Не найдено имя группы.

Так имя компьютера и группы нужны существующие, не буквально же "MYDOMAINCONTROLLER", так?

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

14

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

У Вас на каких служебных юзерах это происходило? Просто у меня подобного не возникало.

На Guest, IUSR_SERV и еще одном, имя не помню, не на работе сейчас. Что с ними не так, не разбирался пока.

15

Re: WSH: блокировка учетных записей, неактивных более 30 дней

The gray Cardinal пишет:

Так имя компьютера и группы нужны существующие, не буквально же "MYDOMAINCONTROLLER", так?

Так я свои и пытался... . Но в открытый доступ их выкладывать не стал .

16

Re: WSH: блокировка учетных записей, неактивных более 30 дней

delpher пишет:

На Guest, IUSR_SERV и еще одном, имя не помню, не на работе сейчас. Что с ними не так, не разбирался пока.

Ага! Каюсь, грешен, просмотрел, что вывод закомментирован. Появились и у меня. Причём поболе десятка. Причины тоже пока не улавливаю .

17

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

Появились и у меня. Причём поболе десятка. Причины тоже пока не улавливаю .

А я посмотрел, что это не люди и разбираться не стал - неактивных людей отбирает нормально, а служебные пусть себе болтаются.

18

Re: WSH: блокировка учетных записей, неактивных более 30 дней

А у меня попадают и ручные, так сказать, учётные записи. Сначала думал, что связано с отключёнными учётными записями (их большинство там) — не подтвердилось.

19

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Примечательно, что спотыкается скрипт на запросе даты последнего входа. Что бы это значило... У Ваших ручных, на которых споткнулось, дата последнего входа где-нибудь видна?

20 (изменено: alexii, 2007-11-22 20:01:29)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

На встроенных учётных записях, типа IUSR_*, IWAM_*, krbtgt & etc атрибуты lastLogon и logonCount отсутствуют вообще.
На ручных, о которых спотыкается, атрибуты lastLogon и logonCount:
1. Отсутствуют.
2. Присутствуют, в виде:

Attribute    Syntax    Count    Value(s)    
lastLogon    Integer8    1    0x0    
logonCount    Integer    1    0

У «нормальных» учётных записей выглядит так:

Attribute    Syntax    Count    Value(s)    
lastLogon    Integer8    1    22.11.2007 17:25:30    
logonCount    Integer    1    835

Возможно, что во втором случае ошибка происходит не от того, что нет атрибута, а в момент вычисления создания объекта Date?

LastLogin = new Date (USER.LastLogin)

21 (изменено: alexii, 2007-11-22 23:40:52)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Поколдовав над JS, понял что не смогу проверить. На основе Вашего скрипта слепил на VBS:

Option Explicit

Dim strDomain
Dim intNumDays

Dim objDomain
Dim objUser

Dim dtLastLogon
Dim intLastLogonDiff

strDomain = "MyDomainName"
intNumDays = 30

Set objDomain = GetObject("WinNT://" & strDomain)
objDomain.Filter = Array("User")

'WScript.Echo "Учетные записи домена " & strDomain & ", последний вход под которыми " & _
'    "был произведен более " & intNumDays & " дней назад."

On Error Resume Next

WScript.Echo "Name, FullName, LastLogon, DayDiff"

For Each objUser In objDomain
    With objUser
        dtLastLogon = .LastLogin
        
        If Err.Number = 0 Then
            intLastLogonDiff = DateDiff("d", .LastLogin, Now())
        
            If intLastLogonDiff >= intNumDays Then
                WScript.Echo .Name & ", " & .FullName & ", " & .LastLogin & ", " & intLastLogonDiff
            End If
        Else
            Err.Clear
            WScript.Echo .Name & ", " & .FullName & ", " & "Not found attribute" & ", " & "Not calculated"
        End If
    End With
Next

'WScript.Echo "=== End =========================================================="

WScript.Quit 0

Кусок результата:

Name, FullName, LastLogon, DayDiff
0Бух, 0Бухгалтер, 21.09.2007 16:05:32, 62
IUSR_XXXX, Гостевая учетная запись Интернета, Not found attribute, Not calculated
IUSR_XXXXXX, Гостевая учетная запись Интернета, Not found attribute, Not calculated
IWAM_XXXX, Учетная запись для запуска IIS, Not found attribute, Not calculated
IWAM_XXXXXX, Гостевая учетная запись Интернета, Not found attribute, Not calculated
krbtgt, , Not found attribute, Not calculated
TestUser, TestUser, 12.07.2007 9:28:47, 133
TsInternetUser, TsInternetUser, Not found attribute, Not calculated
User1C, User1C, 12.12.2006 9:17:49, 345
XXXXX, XXXXX XXXXXX XXXXXXXXX, Not found attribute, Not calculated
XXX, XXX, 09.08.2007 21:10:59, 105

22

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Нет, ошибка всё ж, похоже, возникает в момент запроса атрибута lastLogon, даже если атрибут есть, но выглядит:

Attribute    Syntax    Count    Value(s)    
lastLogon    Integer8    1    0x0

Почему — не понятно.

Пробовал вместо

dtLastLogon = objUser.LastLogin
dtLastLogon = objUser.Get("LastLogin")

с аналогичным результатом.

23 (изменено: delpher, 2007-11-23 10:45:25)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Вот тут есть объяснение.
http://www.sql.ru/forum/actualthread.aspx?tid=352031

В Вашем случае оно подтверждается? В моем - да.

24

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Прочитал. На мой взгляд, не совсем верно. Как я уже писал, скажем, для IUSR_XXXX это верно, атрибут lastLogon в базе AD у этого объекта отсутствует. У некоторого объекта YYYYY (ручками созданная учётная запись, под которой также не было произведено ни одного входа), атрибут lastLogon присутствует, имеет значение 0x0 типа Integer8. Казалось бы, коль атрибут у объекта присутствует в базе, не должно быть ошибки при его запросе, однако ошибка возникает.
Вот у меня возник вопрос, как в этом случае отделить первые от вторых. Номер ошибки одинаковый.

25

Re: WSH: блокировка учетных записей, неактивных более 30 дней

А какая разница? Что так входов не было, что так не было, и все тут. Можно в этом случае анализировать дату создания аккаунта и слишком старых удалять.

26

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Дело в том, что эти учётные записи в список не попадают (мне-то не критично, у меня все новые учётки создаются с disable статусом).

Ещё вопрос. Какие принципиальные отличия будут в случае использования провайдера LDAP вместо WinNT?

P.S. Насчёт удалять — пока будем, в противовес заголовку темы, говорить только об отборе записей. И в случае анализа даты IUSR_XXXX и иже с ним будут старше многих, а их бы по возможности не затрагивать. Не вручную же их исключать?!

27

Re: WSH: блокировка учетных записей, неактивных более 30 дней

У меня в том скрипте при ошибке дата последнего логина выставлялась в текущую и аккаунты "иже с ним" под отбор не попадали.

28 (изменено: delpher, 2007-11-26 09:06:33)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Нашел еще одно извращение Уже чисто на LDAP, без WinNT. Это к вопросу об отличиях
Настраивать не нужно, и у меня отрабатывает без ошибок.

Оригинал с http://www.scriptinganswers.com/vault/A … 0Login.zip

' Retrieve last login time using all domain controllers in default domain.
'  This script will run unmodified in any domain as it retrieves the current domaion context automatically.
'
'  Returns a sorted list of accounts and logins.
'
'

' errors turned off as we are not handling any errors.
Option Explicit
'On Error Resume Next
Dim bTrace

' uncomment next line to see trace output.
'bTrace = true
Dim oRoot
Set oRoot = GetObject("LDAP://rootDSE")
Dim sDomain
sDomain = oRoot.Get("defaultNamingContext")
Dim strLDAP
strLDAP  = "LDAP://OU=Domain Controllers," & sDomain

Const ADS_SCOPE_SUBTREE = 2

Dim objDict
Set objDict = CreateObject("Scripting.Dictionary")

Dim objConnection,objCommand, objRSServers
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 500
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Sort On") = "sAMAccountName"

'Get servers.
objCommand.CommandText = _
    "SELECT sAMAccountName,adsPath FROM '" & strLDAP & "' WHERE objectCategory='computer'"
Set objRSServers = objCommand.Execute

Dim strDC
Do Until objRSServers.EOF
   strDC = objRSServers.Fields("sAMAccountName").Value
   GetUsers Left(strDC, Len(strDC) -1 ), objDict
   objRSServers.MoveNext
Loop


' retrieve sorted values
dim arr, i, j, temp, usr

arr = objDict.Keys()

for i = UBound(arr) - 1 To 0 Step -1
    for j= 0 to i
        if arr(j)>arr(j+1) then
            temp=arr(j+1)
            arr(j+1)=arr(j)
            arr(j)=temp
        end if
    next
next
for each usr in arr
   WScript.Echo "User account:" & usr & "  last login at " & objDict(usr)
next

Function GetUsers( strDC, oDict )
Dim objCommand, objConnection, objRSUsers, dLastLogin
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

    'Get users.
    WScript.Echo "Working on " & strDC
   objCommand.CommandText = _
       "SELECT sAMAccountName, lastLogon FROM 'LDAP://" & strDC & "' WHERE objectCategory='user'"
   Set objRSUsers = objCommand.Execute

   Do Until objRSUsers.EOF
      With objRSUsers
         If Not IsNull( .Fields("lastLogon").Value ) then
            dLastLogin = getLast( .Fields("lastLogon").Value )
            if Not oDict.Exists(.Fields("sAMAccountName").Value) Then
               Trace "ADDING:" & .Fields("sAMAccountName").Value & "  Time: " & dLastLogin
               objDict.Add .Fields("sAMAccountName").Value, dLastLogin
            Else
               If oDict(.Fields("sAMAccountName").Value) < dLastLogin Then
                  Trace "UPDATING:" & .Fields("sAMAccountName").Value
                  oDict(.Fields("sAMAccountName").Value) = dLastLogin
               Else
                  Trace "<<<<<<<<<<SKIPPING:" & .Fields("sAMAccountName").Value
               End If
            End If
         End If
         objRSUsers.MoveNext
      End With
   Loop

End Function

Function getLast( last )
Dim intLastLogonTime
   intLastLogonTime = last.HighPart * (2^32) + last.LowPart
   intLastLogonTime = intLastLogonTime / (60 * 10000000)
   intLastLogonTime = intLastLogonTime / 1440
   getLast = intLastLogonTime + #1/1/1601#
End Function

Function Trace( str )
   if bTrace then
      WScript.Echo str
   end if
End Function

' END OF SCRIPT
'
WScript.Stdin.ReadLine

И мой перевод на JScript - с небольшими изменениями и русскими комментариями.
UPD: исходник перенесен в #35

По-моему уже тему истрепали до самого не хочу и вариантов столько, что чем хочешь, тем и ешь.

29

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Скрипты у нас работают аналогично (я же фактически перевёл Ваш скрипт под, только делал отбор через .Filter(), а не по .Class; да try...catch, увы, в VBS нету, поэтому обработка ошибок сделана чуть иначе), только у Вас, очевидно, нет "ручных" учётных записей, по которым не было входа, поэтому Вы меня таки не поймёте.
Служебные (не знаю, даже как уж правильно обозвать их) учётные записи типа IUSR_XXXX я также хотел бы оставить исключёнными из отбора (даже если они используются (у меня — нет)). А прочие
Скажем, так: создайте учётную запись и, не входя под ней, прогоните Ваш (а если можно, то попутно и мой — для контроля) скрипт (задав numDays = 0). Попадёт ли она в отбор? У меня и на Вашем варианте и на переведённом — такие учётные записи в отбор не попадают.

30

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

Скажем, так: создайте учётную запись и, не входя под ней, прогоните Ваш (а если можно, то попутно и мой — для контроля) скрипт (задав numDays = 0).

Уже только на той неделе, бо и так на работе засиделся. А в пятницу это великий грех

31

Re: WSH: блокировка учетных записей, неактивных более 30 дней

delpher пишет:

По-моему уже тему истрепали до самого не хочу и вариантов столько, что чем хочешь, тем и ешь.

В таком случае просьба сделать резюме этой темы в Коллекции, с отражением всех вменяемых вариантов решения проблемы.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

32

Re: WSH: блокировка учетных записей, неактивных более 30 дней

По варианту VBS:

User account:Гость  last login at 01.01.1601
User account:XXXXXXX  last login at 01.01.1601

Некоторые учётные записи вообще не попали в обработку.

JS:
Вылетело на строке

if (objDict(sAMAccountName) < dLastLogin ) {

C:\Temp\0000\84.js(95, 16) Ошибка выполнения Microsoft JScript: Объект не поддерживает это свойство или метод

P.S. Чтобы есть, нужно быть уверенным, что не отравишься . Игрушки с доменом дорого обходятся.

33

Re: WSH: блокировка учетных записей, неактивных более 30 дней

The gray Cardinal пишет:

В таком случае просьба сделать резюме этой темы в Коллекции, с отражением всех вменяемых вариантов решения проблемы.

Я пока не вижу полноценного решения задачи. Если первый вариант хоть и делает не всё, что нужно, так хотя бы внятно работает; последний же вариант с перебором всех КД и работает горррраздо медленнее и кое-что пропускает по совершенно непонятным пока для меня причинам.

P.S. В принципе, можно запостить старый вариант delpher'а (через провайдер WinNT://), и мою трансляцию его скрипта на VBS; только с обязательным упоминанием, что в обработку не попадают учётные записи, под которыми не было ни одного входа (как встроенные типа IUSR_XXXX, так и обычные). Подождём до следующей недели его подтверждения на мой вопрос четырьмя постами выше?

34

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

Подождём до следующей недели его подтверждения на мой вопрос четырьмя постами выше?

Да, конечно.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

35

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

Скажем, так: создайте учётную запись и, не входя под ней, прогоните Ваш (а если можно, то попутно и мой — для контроля) скрипт (задав numDays = 0). Попадёт ли она в отбор? У меня и на Вашем варианте и на переведённом — такие учётные записи в отбор не попадают.

Создал тестового пользователя. Запустил оба скрипта (из #11 и #21) с numDays=0.
На обоих скриптах что ручной, что системный пользователь - без разницы - если входа не было, то выдается ошибка доступа к lastLogin:

js пишет:

test    !!! - Mon Nov 26 11:01:25 2007 - -2147463155 - Свойства службы каталогов не могут быть найдены в кэше.

vbs пишет:

test, Тест, Not found attribute, Not calculated

Т.е. разницы, кем был создан пользователь - системой или человеком - нету. Так что гарантированного способа отличить их я не вижу.

alexii пишет:

VBS: Некоторые учётные записи вообще не попали в обработку.

Что значит? Не были отобраны совсем?

alexii пишет:

JS:
Вылетело на строке
Код:
if (objDict(sAMAccountName) < dLastLogin ) {
C:\Temp\0000\84.js(95, 16) Ошибка выполнения Microsoft JScript: Объект не поддерживает это свойство или метод

Мой косяк. Исправил:


var Stdout = WScript.Stdout;
var Stdin  = WScript.Stdin;
var objDict = new ActiveXObject("Scripting.Dictionary");
var objConnection = new ActiveXObject("ADODB.Connection");
var objCommand = new ActiveXObject("ADODB.Command");
var oRoot = GetObject("LDAP://rootDSE");
var sDomain = oRoot.Get("defaultNamingContext");

var strLDAP  = "LDAP://OU=Domain Controllers," + sDomain;
var ADS_SCOPE_SUBTREE = 2;
var space = '                                                                                                                                       ';
var strDC, temp, arr

// Формируем запрос на поиск всех серверов домена ========================
objConnection.Provider = "ADsDSOObject";
objConnection.Open("Active Directory Provider");

objCommand.ActiveConnection = objConnection;
objCommand.Properties("Page Size") = 500;
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE;
objCommand.Properties("Sort On") = "sAMAccountName";
objCommand.CommandText = "SELECT sAMAccountName,adsPath FROM '" + strLDAP + "' WHERE objectCategory='computer'";
// =======================================================================

// Выполняем запрос
var objRSServers = objCommand.Execute;

// Перебираем все найденные серверы и добавляем юзеров в глобальный
// для сценария словарь objDict (в функции GetUsers)
while ( !objRSServers.EOF ) {
   strDC = objRSServers.Fields("sAMAccountName").Value;
   GetUsers(strDC.substr(0, strDC.length-1 ));
   objRSServers.moveNext()
}

// сортируем логины по алфавиту
arr = (new VBArray(objDict.Keys())).toArray();
for (var i = arr.length - 1; i>=0; i--) {
   for (var j= 0; j<i; j++) {
      if ( arr[j]>arr[j+1] ) {
         temp=arr[j+1];
         arr[j+1]=arr[j];
         arr[j]=temp;
      }
   }
}

// Перебираем всех найденных юзверей
var name, lastLogon, age
for (var usr in arr)
{
   name = arr[usr];
   lastLogon = objDict(name);

   // Если дата последнего входа не указана, то можно пропустить их,
   // раскомментировав следующую строку
   //if (lastLogon[1] != 0)
   {
      age = (new Date() - lastLogon[0])/(86400*1000);
      Stdout.WriteLine( (name+space).substr(0,30) +
                        "Age: "+(age.toFixed(0)+space).substr(0,10)+
                        "Last logon: " +lastLogon[0].toLocaleString() );
   }
}

Stdout.WriteLine ("\nPress Enter");
Stdin.ReadLine ();


//
// FUNCTIONS
//


// Выбирает всех пользователей на заданном сервере домена
function GetUsers( strDC ) {
var dLastLogin;
var Conn = new ActiveXObject("ADODB.Connection");
var RSUsers = new ActiveXObject ("ADODB.Recordset");
var query = "SELECT sAMAccountName, lastLogon FROM 'LDAP://" + strDC + "' WHERE objectCategory='user'"

   Stdout.WriteLine("Working on " + strDC);

   Conn.Provider = "ADsDSOObject";
   Conn.Open("Active Directory Provider");
   RSUsers.ActiveConnection = Conn;

   for ( RSUsers.Open (query); !RSUsers.EOF; RSUsers.moveNext() ) {
      if ( RSUsers.Fields("lastLogon").Value ) {

         // Для уникальности имени добавляем к нему имя DC
         sAMAccountName = strDC+": "+RSUsers.Fields("sAMAccountName").Value;
         dLastLogin     = RSUsers.Fields("lastLogon").Value;

         dLastLogin = getLast(dLastLogin);

         if (!objDict.Exists (sAMAccountName) ) {
            objDict.Add ( sAMAccountName, dLastLogin );
         } else {
            if (objDict(sAMAccountName) < dLastLogin ) {
               objDict(sAMAccountName) = dLastLogin;
            }
         }
      }
   }
}

// конвертирует lastLogon, возвращенный запросом к LDAP в удобоваримую дату
// возвращает массив - дату как Date() и дату как число
function getLast( last ) {
var res,
    intLastLogonTime = last.HighPart* (4294967296) + last.LowPart;

   res = intLastLogonTime / 10000;
   res = res - Math.abs((new Date(1601,0,1)).getTime());

   return [(new Date(res)), intLastLogonTime]; // возвращаем массив
}

36

Re: WSH: блокировка учетных записей, неактивных более 30 дней

delpher пишет:

Создал тестового пользователя. Запустил оба скрипта (из #11 и #21) с numDays=0.
На обоих скриптах что ручной, что системный пользователь - без разницы - если входа не было, то выдается ошибка доступа к lastLogin:

js пишет:

test    !!! - Mon Nov 26 11:01:25 2007 - -2147463155 - Свойства службы каталогов не могут быть найдены в кэше.

vbs пишет:

test, Тест, Not found attribute, Not calculated

Т.е. разницы, кем был создан пользователь - системой или человеком - нету.

Попробуйте посмотреть через ADSI Edit, Active Directory Administration Tool или Active Directory Explorer; у меня, например, для IUSR_XXXX — lastLogon: Value not set, для ручного XXXX — lastLogon: 0. Так что, какая-то разница есть .

delpher пишет:

Так что гарантированного способа отличить их я не вижу.

Я тоже. Вылетает с одинаковой ошибкой на тех, и на тех. Ну, и что с ними делать? Есть какой-либо атрибут, что ли, чтобы отличить встроенные системные учётные записи от ручных? Может, просто смотреть, если группа == Users, то считать эти учётки == системные? Криво как-то...

delpher пишет:
alexii пишет:

VBS: Некоторые учётные записи вообще не попали в обработку.

Что значит? Не были отобраны совсем?

Угу. Раскомментировал bTrace = True и смотрел. Попробую завтра поразбираться.

delpher пишет:

Мой косяк. Исправил:
...

Тоже завтра погляжу.

37

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Возможно, загвоздка вот в этом куске:

if ( RSUsers.Fields("lastLogon").Value ) {

Тех, у которых lastLogon <> 0 идёт отбирает.

XXXXSERVER: Администратор     Age: 7         Last logon: вторник, 20 ноября 2007 года 13:20:48

Тех, у которых lastLogon = 0 отбирает.

XXXXSERVER: Гость             Age: 148619    Last logon: понедельник, 1 января 1601 года 0:00:00

Тех, у которых lastLogon = <not set> не отбирает.

P.S. В запрос они попадают. Я вставлял вывод strDC+": "+RSUsers.Fields("sAMAccountName").Value перед if ( RSUsers.Fields("lastLogon").Value ) {

38

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

Возможно, загвоздка вот в этом куске:

if ( RSUsers.Fields("lastLogon").Value ) {

Тех, у которых lastLogon <> 0 идёт отбирает.

XXXXSERVER: Администратор     Age: 7         Last logon: вторник, 20 ноября 2007 года 13:20:48

Тех, у которых lastLogon = 0 отбирает.

XXXXSERVER: Гость             Age: 148619    Last logon: понедельник, 1 января 1601 года 0:00:00

Тех, у которых lastLogon = <not set> не отбирает.

P.S. В запрос они попадают. Я вставлял вывод strDC+": "+RSUsers.Fields("sAMAccountName").Value перед if ( RSUsers.Fields("lastLogon").Value ) {

Ко всему выдает, есть ли у атрибута значение, или оно <Not Set>, проверьте.

var Stdout = WScript.Stdout;
var Stdin  = WScript.Stdin;
var objDict = new ActiveXObject("Scripting.Dictionary");
var objConnection = new ActiveXObject("ADODB.Connection");
var objCommand = new ActiveXObject("ADODB.Command");
var oRoot = GetObject("LDAP://rootDSE");
var sDomain = oRoot.Get("defaultNamingContext");

var strLDAP  = "LDAP://OU=Domain Controllers," + sDomain;
var ADS_SCOPE_SUBTREE = 2;
var space = '                                                                                                                                       ';
var strDC, temp, arr

// Формируем запрос на поиск всех серверов домена ========================
objConnection.Provider = "ADsDSOObject";
objConnection.Open("Active Directory Provider");

objCommand.ActiveConnection = objConnection;
objCommand.Properties("Page Size") = 500;
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE;
objCommand.Properties("Sort On") = "sAMAccountName";
objCommand.CommandText = "SELECT sAMAccountName,adsPath FROM '" + strLDAP + "' WHERE objectCategory='computer'";
// =======================================================================

// Выполняем запрос
var objRSServers = objCommand.Execute;

// Перебираем все найденные серверы и добавляем юзеров в глобальный
// для сценария словарь objDict (в функции GetUsers)
while ( !objRSServers.EOF ) {
   strDC = objRSServers.Fields("sAMAccountName").Value;
   GetUsers(strDC.substr(0, strDC.length-1 ));
   objRSServers.moveNext()
}

// сортируем логины по алфавиту
arr = (new VBArray(objDict.Keys())).toArray();
for (var i = arr.length - 1; i>=0; i--) {
   for (var j= 0; j<i; j++) {
      if ( arr[j]>arr[j+1] ) {
         temp=arr[j+1];
         arr[j+1]=arr[j];
         arr[j]=temp;
      }
   }
}

// Перебираем всех найденных юзверей
var name, lastLogon, age, comment, Item
for (var usr in arr)
{
   name = arr[usr];
   Item = objDict(name);
   lastLogon = Item[0];
   comment = Item[1];

   age = (new Date() - lastLogon)/(86400*1000);
   Stdout.WriteLine( (name+space).substr(0,30) +
                     "Age: "+(age.toFixed(0)+space).substr(0,10)+
                     "Last logon: " +(lastLogon.toLocaleString()+space).substr(0,32)+
                     "Comment: " +comment );
}

Stdout.WriteLine ("\nPress Enter");
Stdin.ReadLine ();


//
// FUNCTIONS
//


// Выбирает всех пользователей на заданном сервере домена в ассоциативный массив
function GetUsers( strDC ) {
var dLastLogin;
var Conn = new ActiveXObject("ADODB.Connection");
var RSUsers = new ActiveXObject ("ADODB.Recordset");
var query = "SELECT sAMAccountName, lastLogon FROM 'LDAP://" + strDC + "' WHERE objectCategory='user'"

   Stdout.WriteLine("Working on " + strDC);

   Conn.Provider = "ADsDSOObject";
   Conn.Open("Active Directory Provider");
   RSUsers.ActiveConnection = Conn;

   for ( RSUsers.Open (query); !RSUsers.EOF; RSUsers.moveNext() ) {

      // Для уникальности имени добавляем к нему имя DC
      sAMAccountName = strDC+": "+RSUsers.Fields("sAMAccountName").Value;

      if ( dLastLogin = RSUsers.Fields("lastLogon").Value ) {
          // сюда попадаем, когда у атрибута есть значение (любое)
          dLastLogin     = getLast(dLastLogin);
         AddToDict(sAMAccountName, dLastLogin, '   Success')
      } else {
         // сюда попадаем, когда атрибута нет значения (<Not Set>)
         AddToDict(sAMAccountName, new Date(1900,0,1,0,0,0), '<Not Set>')
      }
   }
}


// добавляет в ассоциативный массив значение с примечанием
function AddToDict(name, value, comment) {
   if (!objDict.Exists (name) ) {
      objDict.Add ( name, [value, comment] );
   } else {
      if (objDict(name)[0] < value ) {
         objDict(name) = [value, comment];
      }
   }
}

// конвертирует lastLogon, возвращенный запросом к LDAP в удобоваримую дату
// возвращает массив - дату как Date() и дату как число
function getLast( last ) {
var res,
    intLastLogonTime = last.HighPart* (4294967296) + last.LowPart;

   res = intLastLogonTime / 10000;
   res = res - Math.abs((new Date(1601,0,1)).getTime());

   return (new Date(res));
}

Но у меня аккаунты с lastLogon = <Not Set> отсутствуют вообще. Т.е., что у системных, что у ручных - если входа не было, то lastlogon = 0 (что равноценно дате 01.01.1601). Так что универсального способа отделить ручных мух от системных котлет все еще нету (если это кому-то критично).

Кстати, посмотрите, какой у Вас режим работы домена: 'Windows 2000 (основной режим)', 'Windows 2000 (смешанный режим)' или 'Windows Server 2003'? И вообще, что за ось на контроллере домена? У меня 2003-я и домен поднят в режиме 'Windows 2000 (смешанный режим)' (поднимал не я).

39

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Домен Windows 2000 основной режим; 2DC; оба, соответственно, Windows 2000 Server SP4 UP1; один узел, один лес. Когда я поднимал его, помнится, некоторое время держал в смешанном (был переход с домена NT 4.0). А может и нет, уже не помню. Домен делал свеженький, со старым, на время перехода, устанавливались доверительные отношения. Возможно, что-то поменялось в политике партии.

delpher пишет:

Но у меня аккаунты с lastLogon = <Not Set> отсутствуют вообще.

Похоже, что мои учётные записи с lastLogon = <Not Set> создавались ещё до появления SP1.

В общем, как всегда, завтра попробую.

OFF:
===============================================================
P.S. Мне, собственно, не нравилось (из первых вариантов скриптов) не очень корректное отслеживание через обработку ошибок try/on error. Судя даже по предпоследнему варианту, работает и с lastLogon = <Not Set>, и с == 0.

P.P.S. Честно говоря, тяжко идёт java, тяжко; собственно, не само чтение/понимание, а отладка/изменение, дабы что-то проверить. Порою мне быстрее на VBS перевести и там проверять, нежели листать документацию в попытке найти то, чего ещё и сам не знаешь.

P.P.P.S. Посмотрев на Ваш вариант с перебором контроллеров, подумал — зачем? Хотел уже написать, что сие лишнее. Лишь заметно позже вспомнил, что lastLogon по умолчанию не реплицируется.
===============================================================

40

Re: WSH: блокировка учетных записей, неактивных более 30 дней

alexii пишет:

P.S. Мне, собственно, не нравилось (из первых вариантов скриптов) не очень корректное отслеживание через обработку ошибок try/on error. Судя даже по предпоследнему варианту, работает и с lastLogon = <Not Set>, и с == 0.

Ну самый последний вариант разделяет 0 и <Not Set> вроде корректно и без try catch. Другое дело, что определить системность учетной записи это в общем случае не помогает.

41

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Уважаемые господа Разработчики!
Неужели в этой теме вообще нет ни одной строки кода, которую можно было бы поместить в Коллекцию? Могу предположить только две причины этого:
а) Вся тема - сплошной флуд.
б) Проблема блокировки учетных записей, неактивных более 30 дней, с помощью скрипта - это проблема, принципиально нерешаемая на современном уровне развития IT-технологий.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

42 (изменено: delpher, 2007-12-04 16:39:21)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Ну если у alexii сказать больше нечего (замечания исходили только от него:) ), то выложу последний свой JS вариант, только причешу его и сочиню аннотацию к нему, поскольку, как выяснилось, он не вполне универсален.
[off] 2 The gray Cardinal: нас не почешешь, так мы и не похрюкаем [/off]

43

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Мне пока нечего сказать, поскольку болен, ergo нет возможности опробовать, что было выложено delpher под конец, как собирался. Вариант js от delpher, на мой взгляд, достаточен (после перехода на ADO вроде бы должны отслеживаться все учётные записи) для Коллекции. Остальное — тонкости в разных подходах к кодированию. Оклемаюсь, опробую, переложу на VBS.

44

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Спасибо! Эту тему открепляю, а дополнять тему в Коллекции в любое время новыми мыслями, естественно, не возбраняется.

Предложения в русском языке начинаются с большой буквы и заканчиваются точкой.
В названии ветки всегда должен быть указан язык программирования или среда исполнения скрипта, если это возможно.

45 (изменено: dmitry_a, 2007-12-06 14:30:20)

Re: WSH: блокировка учетных записей, неактивных более 30 дней

Случайно наткнулся на еще один вариант скрипта выполняющий эту функцию (проверял, работает как надо, системные учетные записи не блокируются)  взят отсюда http://www.codeproject.com/KB/vbscript/ … Users.aspx

 'set up some varaibles
bDisable = 0                            'do you want to disable and move the accounts?
strFileName = "c:\users.txt"                    'the file where the tab delimited results are saved

strUserDN = "domaincontroller/OU=All Users, dc=yourdomain, dc=com"      'initial OU where the users are located
strNewParentDN = "OU=Inactive Users, dc=yourdomain, dc=com"      'location where disabled users are moved to
strDomain = "yourdomain.com"                'FQDN
iDayThreshold = 180                        'number of days without logging in

strOut = ""                            'tmp string
strOut2 = ""                        'another tmp string
Main()

Sub Main()
'get the initial data then ask some questions
    EnumOUs("LDAP://" & strUserDN)

    'yes=6, no=7, cancel=2
    answer = MsgBox(strOut & vbCrLf & "Disable and move these users?", vbYesNoCancel)
    If answer=2 Then
        Exit Sub
    ElseIf answer=6 Then
        bDisable = 1
        EnumOUs("LDAP://" & strUserDN)
    End If

    answer = MsgBox("Save the data to " & strFileName & "?", vbYesNoCancel)

    If answer = 6 Then
        strOut = "username" & vbTab & "Name" & vbTab & "Last Logon" & vbTab & "Days" & vbCrLf & strOut
        strOut2 = "These users have never logged in:" & vbCRLF _
                & "username" & vbTab & "Name" & vbTab & "Creation Date" & vbCRLF & strOut2
        strOut = strOut & vbCRLF & vbCRLF & strOut2
        SaveToFile strOut
    End If
End Sub

Function EnumOUs(sADsPath)
'recursively finds all of the OU's and users in the given AD path

    Set oContainer = GetObject(sADsPath)
    oContainer.Filter = Array("OrganizationalUnit")
    For Each oOU in oContainer
        EnumUsers(oOU.ADsPath)
        EnumOUs(oOU.ADsPath)
    Next
End Function

Function EnumUsers(sADsPath)
'finds all of the users' last login time

    Set oContainer = GetObject(sADsPath)
    oContainer.Filter = Array("User")
    For Each oADobject in oContainer
        Set objLogon = oADobject.Get("lastLogon")
        intLogonTime = objLogon.HighPart * (2^32) + objLogon.LowPart 
        intLogonTime = intLogonTime / (60 * 10000000)
        intLogonTime = intLogonTime / 1440
        intLogonTime = intLogonTime + #1/1/1601#
        inactiveDays = Fix(Now() - intLogonTime)

        'adds a list of people who have never logged on.
        If intLogonTime = "1/1/1601" Then strOut2 = strOut2 & oADobject.sAMAccountName & vbTab & oADobject.DisplayName & vbTab & oADobject.whencreated & vbCRLF
        
        'if they are beyond the threshhold, it will add them to the output string
        If inactiveDays > iDayThreshold And intLogonTime <> "1/1/1601" Then
            strOut = strOut & oADobject.sAMAccountName _
                & vbTab & oADobject.displayName _
                & vbTab & intNewTime _
                & vbTab & intLogonTime _
                & vbTab & intMaxTime _
                & vbTab & inactiveDays & vbCRLF

            'if disabling was requested, it will move them to a new folder and disable the account
            If bDisable = 1 Then
                If strNewParentDN <> "" Then MoveUser oADobject.Name, oADobject.ADsPath
                Set objUser = GetObject("WinNT://" & strDomain & "/" & oADobject.sAMAccountName)
                objUser.AccountDisabled = True
                objUser.SetInfo
            End If
        End If
    Next
End Function

Sub MoveUser(sName, sPath)
'moves the user from the given OU to a new OU
    Set objUser = GetObject("LDAP://" & strNewParentDN)
    objUser.MoveHere sPath, sName
End Sub

Sub SaveToFile(strData)
'writes the given data to a text file
    Dim objFSO
    Set objFSO = CreateObject("Scripting.FileSystemObject") 
    If objFSO.FileExists(strFileName) Then
        Set objTextStream = objFSO.OpenTextFile(strFileName, 2)
        
        objTextStream.Write strData
        objTextStream.Close
        Set objTextStream = Nothing
    Else
        Set objTextStream = objFSO.CreateTextFile(strFileName, True)        
        objTextStream.Write strData
        objTextStream.Close
        Set objTextStream = Nothing
    End If
End Sub