WMI知识学习

2012-03-08  陈斌 

四、常用的脚本对象库

 

WMI脚本对象库由24个对象组成,在脚本中心有一副脚本库对象模型的图,有兴趣的朋友可以参考一下,作为入门,我们一般只用到其中的四个对象,其继承和层级关系如下:

 

SwbemLocator教本库对象→SwbemServicesWMI服务对象→SwbemObjectSet类实例集合对象→SwbemObject类的实例

 

好了,现在让我们来举个例子,详细说明一下这四个对象在脚本中的应用方法:作者:临汾市外事旅游局薛靖澜,转载请注明

 

例一:用来检索计算机上安装的光驱:

 

strComputer = "."

 

Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")

 

Set objSWbemServices = objSWbemLocator.ConnectServer

 

Set colItems = objSWbemServices.ExecQuery("Select * from Win32_CDROMDrive")

 

For Each objItem in colItems

 

 WScript.Echo "光盘驱动器的类型: " & objItem.Caption

 

 WScript.Echo "盘符是: " & objItem.Id

 

Next

 

例二:用来检索CPU型号

 

strComputer = "."

 

Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")

 

Set objSWbemServices = objSWbemLocator.ConnectServer

 

Set objSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Processor")

 

For Each objSWbemObject In objSWbemObjectSet

 

 Wscript.echo  "CPU的型号为:" & objSWbemObject.name

 

Next

 

请注意,这两个脚本虽然简单,却代表了WMI脚本设计中最普遍的东西,可以说是很典型的脚本。让我们来仔细观察一下这两个脚本,讨论讨论一下脚本访问WMI的基本方法:我们可以看到整个脚本的执行过程基本相同:

 

①定义了SwbemLocator的实例;SwbemServices、SwbemObjectSet、SwbemObject对象;创建了SwbemLocator的实例;②通过SwbemLocator的ConnectServer方法连接到WMI,获得SwbemServices的实例集合;③枚举集合中的每个实例;④显示各实例的一些属性。

 

让我们来详细说明一下各行代码的详细含义,并请仔细回想我们第二部分WMI基本结构中谈到的编写WMI脚本的基本步骤:(注意:考虑到脚本的简易,我们编写的脚本一般只在本地计算机进行检索,我们只介绍涉及本地的这一部分,涉及到访问远程计算机的部分我们就省略了,其实随着计算机安全技术的发展,仅凭WMI访问远程计算机的可行性是越来越小了)作者:临汾市外事旅游局薛靖澜,转载请注明

 

1、连接到指定的CIM命名空间

 

要用WMI对象编程,必须首先创建WMI对象脚本库的实例,连接到目标计算机的CIM命名空间。

 

方法一:

 

步骤一、建立SwbemLocator对象的实例。代码为:

 

Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")

 

然后用SwbemLocator对象的ConnectServer方法(SwbemLocator对象只有1个只读属性Security_和1个方法ConnectServer)建立WMI服务的连接,返回一个命名空间的连接(SwbemServices对象),代码为:

 

Set objSWbemServices = objSWbemLocator.ConnectServer()

 

ConnectServer方法共有8个参数,所有参数都是可选的,其参数格式如下:

 

ConnectServer([strComputName],[strNamespace],[strUser],[strPassword],[strLocale],[strAuthority],[iSecurityFlags],[objwbemNamedValueSet])

 

考虑到WMI的复杂性,在使用中我们如果只是在本地计算机上进行检索和查询,那么我们只需要设置第1、2个参数,其它参数都可以省略;如果想连接到远程计算机,一般需要对前4个参数进行设置,我们也只对此做个简单的介绍。

 

strServer——计算机名,缺省为本机,本机也可以用”.”

 

strNamespace——需要登录的CIM命名空间,例如:"root\CIMV2",缺省为"root\CIMV2"。

 

方法二:用moniker名字法建立WMI服务的连接,这也是微软推荐的连接方法作者:临汾市外事旅游局薛靖澜,转载请注明

 

moniker名字法是利用GetObject函数直接建立WMI服务的连接,它的要点就是通过编写一个moniker字符串作为GetObject函数的参数,然后返回一个SwbemServices对象。

 

关于moniker字符串的完整格式如下:

 

"winmgmts:[{SecuritySettings}!][\\ComputerName][\Namespace][:ClassName][.KeyProperty='Value']"

 

"winmgmts:"是前缀, 表示为WMI服务,必须使用;第二部分用来验证权限和假冒级别的,省略。第三部分为计算机名字:"\\.\"是计算机名字,默认可省略,其余同上;第四部分CIM命名空间:缺省的命名空间为"root\CIMV2",默认可省略。

 

第五部分为类名。第六部分为属性值。注意:当该moniker字符串不包括最后2项时(即为:"winmgmts:[\\ComputerName][\Namespace]"),则GetObject(moniker字符串)返回的是一个命名空间的已验证的连接(SwbemServices对象);当不包括最后1项时,返回的是一个CIM类(SWbemObject对象);当包括最后2项时,返回的是一个类的单独实例(SWbemObject对象)。

.获得类的实例

 

我们有4种方法获得类的实例,其中方法1和方法2是通过SwbemServices对象的InstancesOf方法和ExecQuery方法来获得某个类的多个实例组成的集合对象。方法3和方法4则是返回单独的类的实例,即返回的是一个SWbemObject对象。

 

1)InstancesOf方法获得类的实例集合

 

InstancesOf方法的语法参数格式如下:

 

SwbemServices.InstancesOf(strClass)

 

strClass为类名,例如"Win32_Service"

 

回顾例二,就是用语句:Set objSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Processor ") 来获得"Win32 Processor "类的所有实例集合,然后我们可以用

 

For Each objSWbemObject In objSWbemObjectSet

 

……

 

Next 作者:临汾市外事旅游局薛靖澜,转载请注明

 

语句获得每一个类的实例SWbemObject对象,然后就可以根据我们的需要,进行相应的操作。

 

2)ExecQuery方法获得类的实例集合

 

与InstancesOf方法不一样的是,ExecQuery方法可以通过查询语句,只返回匹配部分实例和属性。ExecQuery方法的语法参数格式如下:

 

SwbemServices.ExecQuery(strQuery)

 

strQuery为WMI查询语言(WQL)构造的一个查询语句字符串。

 

例如:作者:临汾市外事旅游局薛靖澜,转载请注明

 

Set objSWbemObjectSet = objSWbemServices.ExecQuery("select ProcessorId from Win32_Processor where DeviceID='cpu0'")

 

3)Get方法获得一个类的实例(SWbemObject对象)

 

此方法也就不必再用 For Each objSWbemObject In objSWbemObjectSet :……:Next 语句从SWbemObjectSet对象中获得每一个类的实例SWbemObject对象,Get方法的语法参数格式如下:

 

SwbemServices.Get([strObjectPath][.KeyProperty='Value'])

 

strObjectPath是类的名字

 

KeyProperty是主键属性名

 

Value是指定的主键属性值

 

这里要注意的是如果要获得一个类的实例,则strObjectPath.KeyProperty='Value'中的任何一项都不能省略,例如:

 

Set objSWbemServices = GetObject("winmgmts:")

 

Set objSWbemObject = objSWbemServices.Get("Win32_Processor.DeviceID='cpu0'")

 

Wscript.echo “CPU的型号为”:" & objSWbemObject.ProcessorId

 

看,结果一样,脚本却简化了不少。

 

4)直接用moniker名字法获得一个类的实例

 

在说明Moniker名字法的时候我们说过,当包括最后2项时,返回的是一个类的单独实例,如:Set objSWbemObject = GetObject("winmgmts:Win32_Processor.DeviceID='cpu0'")

 

Wscript.echo "首枚CPU序列号:" & objSWbemObject.ProcessorId

 

是不是更加简单?仅仅2条语句就获得了CPU的序列号。

 

3.读取类的实例属性,调用类的方法

 

实在是太多了,你可以参照C:/WINDOWS/system32/wbem/cimwin32.mfl文件中,对所有类的属性和方法的描述。也可以用下列代码查询,虽然看起来有点困难,不过看的多了也就明白了。作者:临汾市外事旅游局薛靖澜,转载请注明

 

strClass=inputbox("请输入你要查询的类")

 

strComputer = "."

 

strNameSpace = "root\cimv2"

 

Const wbemFlagUseAmendedQualifiers = &h20000

 

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\" & strNameSpace)

 

Set objClass = objWMIService.Get(strClass, wbemFlagUseAmendedQualifiers)

 

strMOF = objClass.GetObjectText_

 

WScript.Echo strMOF

在上一篇文章中我们说过,WMI中用类对托管资源进行抽象定义和和管理,那么,什么是类?

  类的定义相信在很多面向对象的计算机教材中都能找到,我的理解是:类,是包含了一组独立功能的模块,这个模块由数据和功能组成,其中数据叫做类的属性,完成功能的部分叫做类的方法。我认为类的优点是:类完全可以由张三进行设计,李四可以拿来直接使用,在使用的时候,李四不需要知道类是如何设计、如何实现的,只要知道:这个类可以实现什么样的功能,这个类包含有什么样的方法和属性就可以了。更加重要的是,类还可以进行继承,就像父亲亡故,儿子继承了父亲所有的财产一样,如果我们发现某个类A已经完成了我们需要的大部分功能,我们再去重新设计一个新类来完成我们所有的功能,未免费时费力,根据类可以继承的特性,我们只要声明一个类继承了类A,然后按照需要添加需要的新的数据元素、扩展现有的功能模块,相信工作会简单很多。我看到很多书里都在说代码重用,我不是程序员,不知道所谓的代码重用具体是如何实现的,但是相信类应该在其中起了很重要的作用。

  在微软的教程中,对类从功能和继承关系上进行了详细的说明,我不再赘述,仅仅做一个摘要:

  

 

 

  任何资源如果要求WMI进行托管,至少需要两个文件:提供程序和托管对象格式 (MOF) 文件。提供程序是一个中间件,通过调用托管资源的本地 API 来响应 WMI 请求。

  每一个WMI托管的硬件和软件资源均有一个类来定义,反过来说,一个CIM类就是一个WMI托管资源的模板,而且资源的所有实例都使用这个模板。这些模板是由属性、方法和限定符组成的。这些属性、方法和限定符都定义在MOF文件中。方法和属性是我们经常遇到的,那么什么是限定符呢?限定符是关于类、属性或方法类型的附加修饰符,类是静态的、抽象的、还是关联的,属性是只读的还是可写的,方法的返回参数等,都定义在其中,那么我们如何知道某个类有那些限定符呢?如下代码可以帮助我们方便的获得类的属性、方法和限定符的信息。

 

Code:

strComputer = "."

strNameSpace = "root\cimv2"

strClass=inputbox("请输入你要查询的类")

 

Set objClass = GetObject("winmgmts:\\" & strComputer & _

                         "\" & strNameSpace & ":" & strClass)

 

WScript.Echo strClass & " 的类限定信息如下:"

WScript.Echo "------------------------------"

i = 1

For Each objClassQualifier In objClass.Qualifiers_

    If VarType(objClassQualifier.Value) = (vbVariant + vbArray) Then    '常数 VBVariant 只与 VBArray 一起返回,以表明 VarType 函数的参数是一个 Variant 类型的数组。

        strQualifier = i & ". " & objClassQualifier.Name & " = " & _

                                  Join(objClassQualifier.Value, ",")

    Else

        strQualifier = i & ". " & objClassQualifier.Name & " = " & _

                                  objClassQualifier.Value

    End If

    WScript.Echo strQualifier

    strQualifier = ""

    i = i + 1

Next

 

WScript.Echo

WScript.Echo strClass & " 类的属性和属性限定信息"

WScript.Echo "-------------------------------------------------"

i = 1 : j = 1

For Each objClassProperty In objClass.Properties_

    WScript.Echo i & ". " & objClassProperty.Name

    For Each objPropertyQualifier In objClassProperty.Qualifiers_

        If VarType(objPropertyQualifier.Value) = (vbVariant + vbArray) Then

            strQualifier = i & "." & j & ". " & _

                           objPropertyQualifier.Name & " = " & _

                           Join(objPropertyQualifier.Value, ",")

        Else

            strQualifier = i & "." & j & ". " & _

                           objPropertyQualifier.Name & " = " & _

                           objPropertyQualifier.Value

        End If

        WScript.Echo strQualifier

        strQualifier = ""

        j = j + 1

    Next

    WScript.Echo

    i = i + 1 : j = 1

Next

 

WScript.Echo

WScript.Echo strClass & " 类的方法和方法限定信息"

WScript.Echo "-------------------------------------------------"

i = 1 : j = 1

For Each objClassMethod In objClass.Methods_

    WScript.Echo i & ". " & objClassMethod.Name

    For Each objMethodQualifier In objClassMethod.Qualifiers_

        If VarType(objMethodQualifier.Value) = (vbVariant + vbArray) Then

            strQualifier = i & "." & j & ". " & _

                           objMethodQualifier.Name & " = " & _

                           Join(objMethodQualifier.Value, ",")

        Else

            strQualifier = i & "." & j & ". " & _

                           objMethodQualifier.Name & " = " & _

                           objMethodQualifier.Value

        End If

        WScript.Echo strQualifier

        strQualifier = ""

        j = j + 1

    Next

    WScript.Echo

    i = i + 1 : j = 1

Next

如果我们执行上述代码来查找win32_service类的限定符,一般情况下可以得到如下信息:

win32_service 的类限定信息如下:

------------------------------

1. dynamic = True

2. Locale = 1033

3. provider = CIMWin32

4. SupportsUpdate = True

5. UUID = {8502C4D9-5FBB-11D2-AAC1-006008C78BC7}

 

win32_service 类的属性和属性限定信息

------------------------------------------------------

1. AcceptPause

1.1. CIMTYPE = boolean

1.3. read = True

……

win32_service 类的方法和方法限定信息

-------------------------------------------------

1. StartService

1.1. MappingStrings = Win32API|Service Functions|StartService

1.2. Override = StartService

1.3. ValueMap = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,..

……

  返回的信息很多,我省略了其中的绝大部分,只保留了一部分典型的来说明我们应该如何读懂类的限定信息。

  返回信息的第一部分表明了对于此类的限定信息:

  1. dynamic = True    Abstract——抽象类,Dynamic——动态类,Association——关联类,表明win32_service类是动态类。

  2. Locale = 1033      代表着区域设置或者叫本地化编码,是一组与用户的语言、国家/地区以及文化传统有关的用户首选信息,确定诸如:键盘布局、字母的排序顺序,以及日期、时间、数字、货币等的格式,一般不用管它。

  3. provider = CIMWin32    Provider代表的是类的提供程序,说明win32_service类是由CimWin32.dll提供的。

  4. SupportsUpdate = True    指出类是否支持实例的修改。

  5. UUID = {8502C4D9-5FBB-11D2-AAC1-006008C78BC7}    代表本地计算机上的全球通用唯一的标识符,我不知道有什么用处。

  

  返回信息的第二部分代表了类的属性限定符

  1. AcceptPause

  这第一行表明检索到的类的属性名是:AcceptPause

  1.1. CIMTYPE = boolean    CIMType代表属性的数据类型,说明这个属性是布尔型。

  1.3. read = True     read代表属性可读,而Write则代表属性的值是否可以修改,如果属性没有定义Write限定符,代表什么意思呢?它代表从该类获得的实例的那个属性是不可以改写的,也就是说不能给这个属性赋值。这其实解决了一个困惑我很久的一个问题,为什么我在对很多类的属性进行了赋值后,用Put_方法对修改进行了提交,可是事实上却什么也没有改变。此外还有Key限定符指出该属性是类的键,用于识别在相同资源集合中的托管资源的唯一实例。

  

  返回信息的第三部分代表了类的方法及方法的限定符。现在我们能用到的还不多,就不介绍了,有兴趣的朋友可以参照:http://msdn2.microsoft.com/en-us/library/aa393650.aspx

  需要说明的是,不是在每一个类定义或者属性、方法定义中,这所有的限定符都要出现的,可能只出现了其中的一部分。

  当然,我们也可以从system32\wbem\cimwin32.mof以及system32/wbem/cimwin32.mfl两个文件中读取整个托管资源的类定义和类描述,以及对每个类的属性、方法和限定符的描述,其中包含的信息很多,也包含了更多的类限定符,阅读起来有一定的难度。将上面的例子的代码和vbsedit中包含的Object Browser联合起来,可以得到我们需要的绝大部分的信息。

在上一篇文章中我们说过,WMI中用类对托管资源进行抽象定义和和管理,那么,什么是类?

  类的定义相信在很多面向对象的计算机教材中都能找到,我的理解是:类,是包含了一组独立功能的模块,这个模块由数据和功能组成,其中数据叫做类的属性,完成功能的部分叫做类的方法。我认为类的优点是:类完全可以由张三进行设计,李四可以拿来直接使用,在使用的时候,李四不需要知道类是如何设计、如何实现的,只要知道:这个类可以实现什么样的功能,这个类包含有什么样的方法和属性就可以了。更加重要的是,类还可以进行继承,就像父亲亡故,儿子继承了父亲所有的财产一样,如果我们发现某个类A已经完成了我们需要的大部分功能,我们再去重新设计一个新类来完成我们所有的功能,未免费时费力,根据类可以继承的特性,我们只要声明一个类继承了类A,然后按照需要添加需要的新的数据元素、扩展现有的功能模块,相信工作会简单很多。我看到很多书里都在说代码重用,我不是程序员,不知道所谓的代码重用具体是如何实现的,但是相信类应该在其中起了很重要的作用。

  在微软的教程中,对类从功能和继承关系上进行了详细的说明,我不再赘述,仅仅做一个摘要:

  

 

 

  任何资源如果要求WMI进行托管,至少需要两个文件:提供程序和托管对象格式 (MOF) 文件。提供程序是一个中间件,通过调用托管资源的本地 API 来响应 WMI 请求。

  每一个WMI托管的硬件和软件资源均有一个类来定义,反过来说,一个CIM类就是一个WMI托管资源的模板,而且资源的所有实例都使用这个模板。这些模板是由属性、方法和限定符组成的。这些属性、方法和限定符都定义在MOF文件中。方法和属性是我们经常遇到的,那么什么是限定符呢?限定符是关于类、属性或方法类型的附加修饰符,类是静态的、抽象的、还是关联的,属性是只读的还是可写的,方法的返回参数等,都定义在其中,那么我们如何知道某个类有那些限定符呢?如下代码可以帮助我们方便的获得类的属性、方法和限定符的信息。

 

Code:

strComputer = "."

strNameSpace = "root\cimv2"

strClass=inputbox("请输入你要查询的类")

 

Set objClass = GetObject("winmgmts:\\" & strComputer & _

                         "\" & strNameSpace & ":" & strClass)

 

WScript.Echo strClass & " 的类限定信息如下:"

WScript.Echo "------------------------------"

i = 1

For Each objClassQualifier In objClass.Qualifiers_

    If VarType(objClassQualifier.Value) = (vbVariant + vbArray) Then    '常数 VBVariant 只与 VBArray 一起返回,以表明 VarType 函数的参数是一个 Variant 类型的数组。

        strQualifier = i & ". " & objClassQualifier.Name & " = " & _

                                  Join(objClassQualifier.Value, ",")

    Else

        strQualifier = i & ". " & objClassQualifier.Name & " = " & _

                                  objClassQualifier.Value

    End If

    WScript.Echo strQualifier

    strQualifier = ""

    i = i + 1

Next

 

WScript.Echo

WScript.Echo strClass & " 类的属性和属性限定信息"

WScript.Echo "-------------------------------------------------"

i = 1 : j = 1

For Each objClassProperty In objClass.Properties_

    WScript.Echo i & ". " & objClassProperty.Name

    For Each objPropertyQualifier In objClassProperty.Qualifiers_

        If VarType(objPropertyQualifier.Value) = (vbVariant + vbArray) Then

            strQualifier = i & "." & j & ". " & _

                           objPropertyQualifier.Name & " = " & _

                           Join(objPropertyQualifier.Value, ",")

        Else

            strQualifier = i & "." & j & ". " & _

                           objPropertyQualifier.Name & " = " & _

                           objPropertyQualifier.Value

        End If

        WScript.Echo strQualifier

        strQualifier = ""

        j = j + 1

    Next

    WScript.Echo

    i = i + 1 : j = 1

Next

 

WScript.Echo

WScript.Echo strClass & " 类的方法和方法限定信息"

WScript.Echo "-------------------------------------------------"

i = 1 : j = 1

For Each objClassMethod In objClass.Methods_

    WScript.Echo i & ". " & objClassMethod.Name

    For Each objMethodQualifier In objClassMethod.Qualifiers_

        If VarType(objMethodQualifier.Value) = (vbVariant + vbArray) Then

            strQualifier = i & "." & j & ". " & _

                           objMethodQualifier.Name & " = " & _

                           Join(objMethodQualifier.Value, ",")

        Else

            strQualifier = i & "." & j & ". " & _

                           objMethodQualifier.Name & " = " & _

                           objMethodQualifier.Value

        End If

        WScript.Echo strQualifier

        strQualifier = ""

        j = j + 1

    Next

    WScript.Echo

    i = i + 1 : j = 1

Next

如果我们执行上述代码来查找win32_service类的限定符,一般情况下可以得到如下信息:

win32_service 的类限定信息如下:

------------------------------

1. dynamic = True

2. Locale = 1033

3. provider = CIMWin32

4. SupportsUpdate = True

5. UUID = {8502C4D9-5FBB-11D2-AAC1-006008C78BC7}

 

win32_service 类的属性和属性限定信息

------------------------------------------------------

1. AcceptPause

1.1. CIMTYPE = boolean

1.3. read = True

……

win32_service 类的方法和方法限定信息

-------------------------------------------------

1. StartService

1.1. MappingStrings = Win32API|Service Functions|StartService

1.2. Override = StartService

1.3. ValueMap = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,..

……

  返回的信息很多,我省略了其中的绝大部分,只保留了一部分典型的来说明我们应该如何读懂类的限定信息。

  返回信息的第一部分表明了对于此类的限定信息:

  1. dynamic = True    Abstract——抽象类,Dynamic——动态类,Association——关联类,表明win32_service类是动态类。

  2. Locale = 1033      代表着区域设置或者叫本地化编码,是一组与用户的语言、国家/地区以及文化传统有关的用户首选信息,确定诸如:键盘布局、字母的排序顺序,以及日期、时间、数字、货币等的格式,一般不用管它。

  3. provider = CIMWin32    Provider代表的是类的提供程序,说明win32_service类是由CimWin32.dll提供的。

  4. SupportsUpdate = True    指出类是否支持实例的修改。

  5. UUID = {8502C4D9-5FBB-11D2-AAC1-006008C78BC7}    代表本地计算机上的全球通用唯一的标识符,我不知道有什么用处。

  

  返回信息的第二部分代表了类的属性限定符

  1. AcceptPause

  这第一行表明检索到的类的属性名是:AcceptPause

  1.1. CIMTYPE = boolean    CIMType代表属性的数据类型,说明这个属性是布尔型。

  1.3. read = True     read代表属性可读,而Write则代表属性的值是否可以修改,如果属性没有定义Write限定符,代表什么意思呢?它代表从该类获得的实例的那个属性是不可以改写的,也就是说不能给这个属性赋值。这其实解决了一个困惑我很久的一个问题,为什么我在对很多类的属性进行了赋值后,用Put_方法对修改进行了提交,可是事实上却什么也没有改变。此外还有Key限定符指出该属性是类的键,用于识别在相同资源集合中的托管资源的唯一实例。

  

  返回信息的第三部分代表了类的方法及方法的限定符。现在我们能用到的还不多,就不介绍了,有兴趣的朋友可以参照:http://msdn2.microsoft.com/en-us/library/aa393650.aspx

  需要说明的是,不是在每一个类定义或者属性、方法定义中,这所有的限定符都要出现的,可能只出现了其中的一部分。

  当然,我们也可以从system32\wbem\cimwin32.mof以及system32/wbem/cimwin32.mfl两个文件中读取整个托管资源的类定义和类描述,以及对每个类的属性、方法和限定符的描述,其中包含的信息很多,也包含了更多的类限定符,阅读起来有一定的难度。将上面的例子的代码和vbsedit中包含的Object Browser联合起来,可以得到我们需要的绝大部分的信息。

557°/5572 人阅读/0 条评论 发表评论

登录 后发表评论